splent-cli 1.7.0__tar.gz → 1.8.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 (167) hide show
  1. {splent_cli-1.7.0/src/splent_cli.egg-info → splent_cli-1.8.0}/PKG-INFO +2 -1
  2. {splent_cli-1.7.0 → splent_cli-1.8.0}/pyproject.toml +2 -1
  3. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_pyproject.py +53 -14
  4. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/export/export_puml.py +100 -25
  5. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_release.py +22 -9
  6. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_containers.py +3 -3
  7. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/services/preflight.py +50 -0
  8. {splent_cli-1.7.0 → splent_cli-1.8.0/src/splent_cli.egg-info}/PKG-INFO +2 -1
  9. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli.egg-info/requires.txt +1 -0
  10. {splent_cli-1.7.0 → splent_cli-1.8.0}/LICENSE +0 -0
  11. {splent_cli-1.7.0 → splent_cli-1.8.0}/README.md +0 -0
  12. {splent_cli-1.7.0 → splent_cli-1.8.0}/setup.cfg +0 -0
  13. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/__init__.py +0 -0
  14. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/__main__.py +0 -0
  15. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/cli.py +0 -0
  16. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/__init__.py +0 -0
  17. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  18. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  19. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  20. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  21. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  22. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  23. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  24. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  25. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  26. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/__init__.py +0 -0
  27. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  28. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  29. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_env.py +0 -0
  30. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_features.py +0 -0
  31. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_github.py +0 -0
  32. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  33. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_product.py +0 -0
  34. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  35. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  36. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  37. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  38. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  39. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/command/__init__.py +0 -0
  40. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/command/command_create.py +0 -0
  41. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/coverage.py +0 -0
  42. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_console.py +0 -0
  43. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  44. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  45. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_reset.py +0 -0
  46. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  47. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  48. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  49. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_status.py +0 -0
  50. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  51. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/doctor.py +0 -0
  52. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/env/env_list.py +0 -0
  53. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/env/env_set.py +0 -0
  54. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/env/env_show.py +0 -0
  55. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/export/__init__.py +0 -0
  56. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  57. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  58. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  59. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  60. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  61. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  62. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  63. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  64. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  65. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  66. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  67. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  68. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  69. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  70. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  71. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  72. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  73. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  74. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  75. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  76. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  77. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  78. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  79. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  80. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  81. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  82. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  83. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  84. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  85. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  86. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  87. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  88. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  89. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
  90. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  91. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  92. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  93. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  94. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  95. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/lint.py +0 -0
  96. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/locust.py +0 -0
  97. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/__init__.py +0 -0
  98. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  99. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_build.py +0 -0
  100. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  101. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  102. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_config.py +0 -0
  103. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  104. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_console.py +0 -0
  105. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_create.py +0 -0
  106. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  107. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  108. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  109. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_down.py +0 -0
  110. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  111. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_env.py +0 -0
  112. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_list.py +0 -0
  113. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  114. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  115. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_port.py +0 -0
  116. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_release.py +0 -0
  117. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  118. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  119. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  120. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_run.py +0 -0
  121. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_select.py +0 -0
  122. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  123. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  124. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  125. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_test.py +0 -0
  126. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_up.py +0 -0
  127. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  128. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/release/__init__.py +0 -0
  129. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/release/release_core.py +0 -0
  130. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/selenium.py +0 -0
  131. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  132. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  133. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  134. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  135. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  136. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  137. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  138. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  139. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  140. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  141. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  142. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  143. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  144. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/commands/version.py +0 -0
  145. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/services/__init__.py +0 -0
  146. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/services/compose.py +0 -0
  147. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/services/context.py +0 -0
  148. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/services/release.py +0 -0
  149. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/__init__.py +0 -0
  150. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/cache_utils.py +0 -0
  151. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/command_loader.py +0 -0
  152. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  153. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/db_utils.py +0 -0
  154. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/decorators.py +0 -0
  155. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  156. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/feature_installer.py +0 -0
  157. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/feature_utils.py +0 -0
  158. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/git_url.py +0 -0
  159. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/integrity.py +0 -0
  160. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/lifecycle.py +0 -0
  161. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/manifest.py +0 -0
  162. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/path_utils.py +0 -0
  163. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli/utils/template_drift.py +0 -0
  164. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli.egg-info/SOURCES.txt +0 -0
  165. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  166. {splent_cli-1.7.0 → splent_cli-1.8.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  167. {splent_cli-1.7.0 → splent_cli-1.8.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.7.0
3
+ Version: 1.8.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
@@ -10,6 +10,7 @@ Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: click==8.1.8
12
12
  Requires-Dist: packaging>=24.0
13
+ Requires-Dist: requests>=2.31.0
13
14
  Provides-Extra: dev
14
15
  Requires-Dist: setuptools==80.3.1; extra == "dev"
15
16
  Requires-Dist: pytest==8.3.4; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.7.0"
7
+ version = "1.8.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"
@@ -14,6 +14,7 @@ license-files = ["LICENSE"]
14
14
  dependencies = [
15
15
  "click==8.1.8",
16
16
  "packaging>=24.0",
17
+ "requests>=2.31.0",
17
18
  ]
18
19
 
19
20
  [project.optional-dependencies]
@@ -9,7 +9,6 @@ import click
9
9
  import tomllib
10
10
 
11
11
  from splent_cli.services import context
12
- from splent_cli.utils.feature_utils import read_features_from_data
13
12
 
14
13
 
15
14
  def _find_missing_pkgs(deps: list) -> list:
@@ -88,29 +87,69 @@ def check_pyproject():
88
87
  _ok(f"All {len(deps)} dependencies satisfied")
89
88
 
90
89
  # Features
91
- features = read_features_from_data(data)
92
- _ok(f"{len(features)} features declared in [tool.splent.features]")
93
-
94
90
  splent = data.get("tool", {}).get("splent", {})
91
+ base_feats = splent.get("features", [])
95
92
  dev_feats = splent.get("features_dev", [])
96
93
  prod_feats = splent.get("features_prod", [])
94
+
95
+ _ok(f"{len(base_feats)} features in [tool.splent].features")
97
96
  if dev_feats:
98
97
  _ok(f"{len(dev_feats)} dev-only features")
99
98
  if prod_feats:
100
99
  _ok(f"{len(prod_feats)} prod-only features")
101
100
 
102
- # UVL
103
- uvl_cfg = splent.get("uvl", {})
104
- uvl_file = uvl_cfg.get("file")
105
- if uvl_file:
106
- app_path = os.path.join(workspace, product)
107
- uvl_path = os.path.join(app_path, "uvl", uvl_file)
108
- if os.path.exists(uvl_path):
109
- _ok(f"UVL file found: uvl/{uvl_file}")
101
+ # Check for duplicates across feature lists
102
+ def _bare_name(entry):
103
+ name = entry.split("/")[-1] if "/" in entry else entry
104
+ return name.split("@")[0]
105
+
106
+ all_entries = base_feats + dev_feats + prod_feats
107
+ seen_names = {}
108
+ for entry in all_entries:
109
+ bare = _bare_name(entry)
110
+ seen_names.setdefault(bare, []).append(entry)
111
+
112
+ for bare, entries in seen_names.items():
113
+ if len(entries) > 1:
114
+ short = bare.replace("splent_feature_", "")
115
+ _fail(f"Duplicate feature '{short}': {', '.join(entries)}")
116
+
117
+ # Check for inconsistent namespaces
118
+ namespaces = set()
119
+ for entry in all_entries:
120
+ if "/" in entry:
121
+ ns = entry.split("/")[0]
122
+ namespaces.add(ns)
123
+ if len(namespaces) > 1:
124
+ _warn(
125
+ f"Inconsistent namespaces: {', '.join(sorted(namespaces))} — use one format consistently"
126
+ )
127
+
128
+ # SPL / UVL
129
+ spl_name = splent.get("spl")
130
+ if spl_name:
131
+ uvl_path = os.path.join(
132
+ workspace, "splent_catalog", spl_name, f"{spl_name}.uvl"
133
+ )
134
+ if os.path.isfile(uvl_path):
135
+ _ok(f"SPL catalog: {spl_name} (UVL found)")
110
136
  else:
111
- _fail(f"UVL file not found: uvl/{uvl_file}")
137
+ _fail(
138
+ f"SPL catalog: {spl_name} — UVL not found at splent_catalog/{spl_name}/{spl_name}.uvl"
139
+ )
112
140
  else:
113
- _warn("No UVL file configured")
141
+ # Legacy fallback
142
+ uvl_cfg = splent.get("uvl", {})
143
+ uvl_file = uvl_cfg.get("file")
144
+ if uvl_file:
145
+ app_path = os.path.join(workspace, product)
146
+ uvl_path = os.path.join(app_path, "uvl", uvl_file)
147
+ if os.path.exists(uvl_path):
148
+ _ok(f"UVL file found: uvl/{uvl_file} (legacy)")
149
+ else:
150
+ _fail(f"UVL file not found: uvl/{uvl_file}")
151
+ else:
152
+ _warn("No SPL configured — set [tool.splent].spl in pyproject.toml")
114
153
 
115
154
  click.echo()
116
155
  if fail:
@@ -149,8 +149,8 @@ def _parse_models(models_path: str) -> list[dict]:
149
149
  text = "\n".join(joined_lines)
150
150
 
151
151
  classes = []
152
- # Split by class definition
153
- class_blocks = re.split(r"^(class \w+\(.*?\):)", text, flags=re.MULTILINE)
152
+ # Split by class definition (with or without parentheses for mixins)
153
+ class_blocks = re.split(r"^(class \w+(?:\(.*?\))?:)", text, flags=re.MULTILINE)
154
154
 
155
155
  i = 1
156
156
  while i < len(class_blocks):
@@ -158,7 +158,7 @@ def _parse_models(models_path: str) -> list[dict]:
158
158
  body = class_blocks[i + 1] if i + 1 < len(class_blocks) else ""
159
159
  i += 2
160
160
 
161
- class_m = re.match(r"class (\w+)\(", header)
161
+ class_m = re.match(r"class (\w+)", header)
162
162
  if not class_m:
163
163
  continue
164
164
  class_name = class_m.group(1)
@@ -213,8 +213,11 @@ def _parse_models(models_path: str) -> list[dict]:
213
213
  if rel_m:
214
214
  rel_name = rel_m.group(1)
215
215
  rel_args = rel_m.group(2)
216
- target_m = re.match(r"(\w+)", rel_args)
217
- target = target_m.group(1) if target_m else "?"
216
+ # Target can be unquoted (User) or quoted ("User" / 'User')
217
+ target_m = re.match(r"""[\"']?(\w+)[\"']?""", rel_args)
218
+ target = target_m.group(1) if target_m else None
219
+ if not target:
220
+ continue
218
221
  uselist = "uselist=False" not in rel_args
219
222
  relationships.append(
220
223
  {
@@ -668,11 +671,16 @@ def _render_exports(
668
671
  )
669
672
  except subprocess.CalledProcessError as e:
670
673
  click.secho("❌ PlantUML failed to render diagram.", fg="red")
671
- if os.getenv("SPLENT_DEBUG"):
672
- stderr = e.stderr
673
- if isinstance(stderr, bytes):
674
- stderr = stderr.decode(errors="replace")
675
- click.secho(stderr[:500], fg="bright_black")
674
+ stderr = e.stderr
675
+ if isinstance(stderr, bytes):
676
+ stderr = stderr.decode(errors="replace")
677
+ if stderr:
678
+ click.secho(stderr.strip(), fg="bright_black")
679
+ stdout = e.stdout
680
+ if isinstance(stdout, bytes):
681
+ stdout = stdout.decode(errors="replace")
682
+ if stdout:
683
+ click.secho(stdout.strip(), fg="bright_black")
676
684
  return
677
685
 
678
686
  try:
@@ -780,21 +788,41 @@ def export_puml(
780
788
  product = context.require_app()
781
789
  product_dir = os.path.join(workspace, product)
782
790
 
783
- # Read UVL
784
- try:
785
- uvl_cfg = PyprojectReader.for_product(product_dir).uvl_config
786
- except (FileNotFoundError, RuntimeError) as e:
787
- click.secho(f"❌ Cannot read pyproject.toml: {e}", fg="red")
788
- raise SystemExit(1)
789
-
790
- uvl_file = uvl_cfg.get("file")
791
- if not uvl_file:
792
- click.secho("❌ No UVL file configured in [tool.splent.uvl].", fg="red")
793
- raise SystemExit(1)
794
-
795
- uvl_path = os.path.join(product_dir, "uvl", uvl_file)
796
- if not os.path.isfile(uvl_path):
797
- click.secho(f"❌ UVL file not found: {uvl_path}", fg="red")
791
+ # Read UVL — try SPL catalog first, fall back to legacy [tool.splent.uvl]
792
+ import tomllib
793
+
794
+ uvl_path = None
795
+ pyproject_path = os.path.join(product_dir, "pyproject.toml")
796
+ if os.path.isfile(pyproject_path):
797
+ with open(pyproject_path, "rb") as f:
798
+ pydata = tomllib.load(f)
799
+ spl_name = pydata.get("tool", {}).get("splent", {}).get("spl")
800
+ if spl_name:
801
+ candidate = os.path.join(
802
+ workspace, "splent_catalog", spl_name, f"{spl_name}.uvl"
803
+ )
804
+ if os.path.isfile(candidate):
805
+ uvl_path = candidate
806
+
807
+ # Legacy fallback: [tool.splent.uvl].file
808
+ if not uvl_path:
809
+ try:
810
+ uvl_cfg = PyprojectReader.for_product(product_dir).uvl_config
811
+ uvl_file = uvl_cfg.get("file")
812
+ if uvl_file:
813
+ candidate = os.path.join(product_dir, "uvl", uvl_file)
814
+ if os.path.isfile(candidate):
815
+ uvl_path = candidate
816
+ except (FileNotFoundError, RuntimeError):
817
+ pass
818
+
819
+ if not uvl_path:
820
+ click.secho(
821
+ "❌ No UVL file found.\n"
822
+ " Set [tool.splent].spl in pyproject.toml (SPL catalog)\n"
823
+ " or [tool.splent.uvl].file (legacy).",
824
+ fg="red",
825
+ )
798
826
  raise SystemExit(1)
799
827
 
800
828
  click.echo(f"📖 Reading UVL model: {uvl_path}")
@@ -848,6 +876,53 @@ def export_puml(
848
876
  if parsed:
849
877
  all_models[package] = parsed
850
878
  break
879
+ # Apply refinement mixins: merge mixin attributes into target models
880
+ for package, fpath in feature_paths.items():
881
+ pyproject_path = os.path.join(fpath, "pyproject.toml")
882
+ if not os.path.isfile(pyproject_path):
883
+ continue
884
+ import tomllib as _tomllib
885
+
886
+ with open(pyproject_path, "rb") as f:
887
+ feat_data = _tomllib.load(f)
888
+ extends = (
889
+ feat_data.get("tool", {})
890
+ .get("splent", {})
891
+ .get("refinement", {})
892
+ .get("extends", {})
893
+ .get("models", [])
894
+ )
895
+ if not extends:
896
+ continue
897
+ # Parse the mixin's models.py
898
+ mixin_models = []
899
+ src_root = os.path.join(fpath, "src")
900
+ if os.path.isdir(src_root):
901
+ for org_dir in os.listdir(src_root):
902
+ mpath = os.path.join(src_root, org_dir, package, "models.py")
903
+ if os.path.isfile(mpath):
904
+ mixin_models = _parse_models(mpath)
905
+ break
906
+ # Merge each mixin into its target model
907
+ for ext in extends:
908
+ target_name = ext.get("target")
909
+ mixin_name = ext.get("mixin")
910
+ if not target_name or not mixin_name:
911
+ continue
912
+ # Find the mixin class in parsed mixin_models
913
+ mixin_cls = next(
914
+ (m for m in mixin_models if m["name"] == mixin_name), None
915
+ )
916
+ if not mixin_cls:
917
+ continue
918
+ # Find the target model across all parsed features
919
+ for feat_models in all_models.values():
920
+ for model in feat_models:
921
+ if model["name"] == target_name:
922
+ model["attributes"].extend(mixin_cls["attributes"])
923
+ model["methods"].extend(mixin_cls["methods"])
924
+ break
925
+
851
926
  click.echo(
852
927
  f"📋 Parsed models from {len(all_models)}/{len(feature_paths)} features."
853
928
  )
@@ -273,14 +273,24 @@ def _scan_dependencies(
273
273
  if f"splent_feature_{short_name}" != own_feature_name:
274
274
  required_features.add(short_name)
275
275
 
276
- for var in re.findall(
277
- r"""os\.(?:getenv|environ\.get)\s*\(\s*['"]([A-Z][A-Z0-9_]+)['"]""", text
278
- ):
279
- env_vars.add(var)
280
- for var in re.findall(
281
- r"""os\.environ\s*\[\s*['"]([A-Z][A-Z0-9_]+)['"]""", text
282
- ):
283
- env_vars.add(var)
276
+ for line in text.splitlines():
277
+ stripped = line.lstrip()
278
+ # Skip comments and docstrings
279
+ if (
280
+ stripped.startswith("#")
281
+ or stripped.startswith('"""')
282
+ or stripped.startswith("'''")
283
+ ):
284
+ continue
285
+ for var in re.findall(
286
+ r"""os\.(?:getenv|environ\.get)\s*\(\s*['"]([A-Z][A-Z0-9_]+)['"]""",
287
+ stripped,
288
+ ):
289
+ env_vars.add(var)
290
+ for var in re.findall(
291
+ r"""os\.environ\s*\[\s*['"]([A-Z][A-Z0-9_]+)['"]""", stripped
292
+ ):
293
+ env_vars.add(var)
284
294
 
285
295
  return sorted(required_features), sorted(env_vars)
286
296
 
@@ -330,12 +340,14 @@ def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> No
330
340
  text = path.read_text()
331
341
 
332
342
  existing_description = f"{feature_name} feature"
343
+ existing_env = None
333
344
  try:
334
345
  data = tomllib.loads(text)
335
346
  splent_contract = data.get("tool", {}).get("splent", {}).get("contract", {})
336
347
  desc = splent_contract.get("description")
337
348
  if desc:
338
349
  existing_description = desc
350
+ existing_env = splent_contract.get("env")
339
351
  except Exception:
340
352
  pass
341
353
 
@@ -373,7 +385,8 @@ def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> No
373
385
  "# Do not edit manually — re-run `splent feature:contract --write` to refresh.\n"
374
386
  "[tool.splent.contract]\n"
375
387
  f'description = "{existing_description}"\n'
376
- "\n"
388
+ + (f'env = "{existing_env}"\n' if existing_env else "")
389
+ + "\n"
377
390
  "[tool.splent.contract.provides]\n"
378
391
  f"routes = {_toml_list(contract['routes'])}\n"
379
392
  f"blueprints = {_toml_list(contract['blueprints'])}\n"
@@ -235,10 +235,10 @@ def product_docker(env_dev, env_prod):
235
235
  seen.add(key)
236
236
  unique.append((label, port, kind))
237
237
 
238
- http_services = [(l, p) for l, p, k in unique if k == "http"]
239
- tcp_services = [(l, p) for l, p, k in unique if k == "tcp"]
238
+ http_services = [(lbl, p) for lbl, p, k in unique if k == "http"]
239
+ tcp_services = [(lbl, p) for lbl, p, k in unique if k == "tcp"]
240
240
  all_services = http_services + tcp_services
241
- label_width = max((len(l) for l, _ in all_services), default=20) + 2
241
+ label_width = max((len(lbl) for lbl, _ in all_services), default=20) + 2
242
242
 
243
243
  if http_services:
244
244
  click.secho(" Accessible services:", bold=True)
@@ -106,6 +106,56 @@ def run_preflight(*, interactive: bool = True, build_mode: bool = False) -> bool
106
106
 
107
107
  failed = False
108
108
 
109
+ # Phase 0: pyproject sanity (duplicates, namespaces, SPL)
110
+ splent_cfg = data.get("tool", {}).get("splent", {})
111
+ base_feats = splent_cfg.get("features", [])
112
+ dev_feats = splent_cfg.get("features_dev", [])
113
+ prod_feats = splent_cfg.get("features_prod", [])
114
+
115
+ all_entries = base_feats + dev_feats + prod_feats
116
+ seen = {}
117
+ for entry in all_entries:
118
+ bare = (
119
+ entry.split("/")[-1].split("@")[0] if "/" in entry else entry.split("@")[0]
120
+ )
121
+ seen.setdefault(bare, []).append(entry)
122
+
123
+ duplicates = {k: v for k, v in seen.items() if len(v) > 1}
124
+ if duplicates:
125
+ if interactive:
126
+ for bare, entries in duplicates.items():
127
+ short = bare.replace("splent_feature_", "")
128
+ click.secho(
129
+ f" pyproject duplicate '{short}': {', '.join(entries)}", fg="red"
130
+ )
131
+ failed = True
132
+ else:
133
+ namespaces = set()
134
+ for entry in all_entries:
135
+ if "/" in entry:
136
+ namespaces.add(entry.split("/")[0])
137
+ if len(namespaces) > 1:
138
+ if interactive:
139
+ click.secho(
140
+ f" pyproject inconsistent namespaces: {', '.join(sorted(namespaces))}",
141
+ fg="yellow",
142
+ )
143
+
144
+ spl_name = splent_cfg.get("spl")
145
+ if spl_name:
146
+ uvl_check = os.path.join(
147
+ workspace, "splent_catalog", spl_name, f"{spl_name}.uvl"
148
+ )
149
+ if not os.path.isfile(uvl_check):
150
+ if interactive:
151
+ click.secho(
152
+ f" pyproject SPL '{spl_name}' — UVL not found in catalog", fg="red"
153
+ )
154
+ failed = True
155
+
156
+ if failed:
157
+ return False
158
+
109
159
  # Phase 1: SAT
110
160
  try:
111
161
  sat_ok, selected, _, _ = _run_sat_check(workspace, app_name, data, None, False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.7.0
3
+ Version: 1.8.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
@@ -10,6 +10,7 @@ Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: click==8.1.8
12
12
  Requires-Dist: packaging>=24.0
13
+ Requires-Dist: requests>=2.31.0
13
14
  Provides-Extra: dev
14
15
  Requires-Dist: setuptools==80.3.1; extra == "dev"
15
16
  Requires-Dist: pytest==8.3.4; extra == "dev"
@@ -1,5 +1,6 @@
1
1
  click==8.1.8
2
2
  packaging>=24.0
3
+ requests>=2.31.0
3
4
 
4
5
  [dev]
5
6
  setuptools==80.3.1
File without changes
File without changes
File without changes