splent-cli 1.41.0__tar.gz → 1.42.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 (296) hide show
  1. {splent_cli-1.41.0/src/splent_cli.egg-info → splent_cli-1.42.0}/PKG-INFO +1 -1
  2. {splent_cli-1.41.0 → splent_cli-1.42.0}/pyproject.toml +1 -1
  3. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/cli.py +93 -34
  4. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_test.py +8 -2
  5. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_commands.py +71 -37
  6. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_test.py +74 -4
  7. splent_cli-1.42.0/src/splent_cli/services/own_commands.py +76 -0
  8. {splent_cli-1.41.0 → splent_cli-1.42.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  9. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/SOURCES.txt +1 -0
  10. {splent_cli-1.41.0 → splent_cli-1.42.0}/COPYING +0 -0
  11. {splent_cli-1.41.0 → splent_cli-1.42.0}/COPYING.LESSER +0 -0
  12. {splent_cli-1.41.0 → splent_cli-1.42.0}/README.md +0 -0
  13. {splent_cli-1.41.0 → splent_cli-1.42.0}/setup.cfg +0 -0
  14. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/__init__.py +0 -0
  15. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/__main__.py +0 -0
  16. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/__init__.py +0 -0
  17. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  18. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  19. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  20. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  21. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  22. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  23. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  24. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  25. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  26. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/__init__.py +0 -0
  27. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
  28. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  29. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  30. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_env.py +0 -0
  31. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_features.py +0 -0
  32. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_github.py +0 -0
  33. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  34. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_product.py +0 -0
  35. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  36. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  37. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_releases.py +0 -0
  38. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
  39. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  40. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  41. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  42. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  43. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/command/__init__.py +0 -0
  44. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/command/command_create.py +0 -0
  45. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/coverage.py +0 -0
  46. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_console.py +0 -0
  47. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  48. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  49. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_reset.py +0 -0
  50. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  51. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  52. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  53. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_status.py +0 -0
  54. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  55. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/doctor.py +0 -0
  56. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/env/env_list.py +0 -0
  57. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/env/env_set.py +0 -0
  58. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/env/env_show.py +0 -0
  59. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/export/__init__.py +0 -0
  60. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  61. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  62. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  63. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  64. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  65. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  66. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  67. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  68. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  69. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  70. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  71. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  72. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  73. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  74. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  75. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  76. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  77. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  78. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  79. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  80. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
  81. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  82. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  83. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  84. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  85. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  86. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  87. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  88. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  89. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  90. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
  91. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  92. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  93. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  94. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  95. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  96. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  97. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  98. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  99. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  100. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  101. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/lint.py +0 -0
  102. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/locust.py +0 -0
  103. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/login.py +0 -0
  104. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
  105. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  106. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  107. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  108. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/__init__.py +0 -0
  109. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  110. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_build.py +0 -0
  111. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  112. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_config.py +0 -0
  113. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  114. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_console.py +0 -0
  115. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  116. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_create.py +0 -0
  117. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  118. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  119. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  120. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_down.py +0 -0
  121. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  122. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_env.py +0 -0
  123. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_list.py +0 -0
  124. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  125. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  126. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_params.py +0 -0
  127. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_port.py +0 -0
  128. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_release.py +0 -0
  129. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  130. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  131. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  132. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_run.py +0 -0
  133. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_select.py +0 -0
  134. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  135. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  136. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  137. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_up.py +0 -0
  138. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  139. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/release/__init__.py +0 -0
  140. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/release/release_core.py +0 -0
  141. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/release/release_resume.py +0 -0
  142. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/selenium.py +0 -0
  143. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  144. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  145. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  146. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  147. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  148. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  149. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  150. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  151. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  152. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  153. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  154. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  155. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  156. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  157. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  158. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  159. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  160. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  161. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/commands/version.py +0 -0
  162. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/__init__.py +0 -0
  163. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/compose.py +0 -0
  164. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/context.py +0 -0
  165. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/credentials.py +0 -0
  166. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/deploy_bundle.py +0 -0
  167. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/image_registry.py +0 -0
  168. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/index_refresh.py +0 -0
  169. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/marketplace.py +0 -0
  170. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/marketplace_api.py +0 -0
  171. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/marketplace_url.py +0 -0
  172. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/neutral_app.py +0 -0
  173. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/preflight.py +0 -0
  174. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/pypi_quota.py +0 -0
  175. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/registry.py +0 -0
  176. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/release.py +0 -0
  177. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/release_dry_run.py +0 -0
  178. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/release_gate.py +0 -0
  179. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/spl_store.py +0 -0
  180. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/services/web_container.py +0 -0
  181. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
  182. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
  183. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
  184. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
  185. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
  186. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
  187. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
  188. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
  189. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
  190. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
  191. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
  192. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
  193. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
  194. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
  195. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
  196. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
  197. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
  198. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
  199. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
  200. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
  201. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
  202. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
  203. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
  204. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
  205. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
  206. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
  207. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
  208. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
  209. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
  210. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
  211. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
  212. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
  213. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
  214. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
  215. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
  216. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/licenses/COPYING +0 -0
  217. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
  218. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
  219. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
  220. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
  221. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
  222. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
  223. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
  224. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
  225. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
  226. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
  227. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
  228. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
  229. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
  230. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
  231. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_400.html +0 -0
  232. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_401.html +0 -0
  233. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_404.html +0 -0
  234. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_500.html +0 -0
  235. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
  236. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
  237. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
  238. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
  239. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
  240. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_app.css +0 -0
  241. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_app.js +0 -0
  242. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
  243. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
  244. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
  245. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
  246. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
  247. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
  248. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
  249. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
  250. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
  251. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
  252. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
  253. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
  254. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
  255. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_own.css +0 -0
  256. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
  257. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
  258. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
  259. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/__init__.py +0 -0
  260. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/archetype.py +0 -0
  261. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/cache_utils.py +0 -0
  262. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/ci_services.py +0 -0
  263. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/ci_workflow.py +0 -0
  264. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/command_loader.py +0 -0
  265. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  266. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/db_client.py +0 -0
  267. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/db_utils.py +0 -0
  268. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/decorators.py +0 -0
  269. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  270. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_installer.py +0 -0
  271. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_naming.py +0 -0
  272. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_params.py +0 -0
  273. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_requirements.py +0 -0
  274. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_resolution.py +0 -0
  275. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/feature_utils.py +0 -0
  276. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/forge.py +0 -0
  277. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/git_url.py +0 -0
  278. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/integrity.py +0 -0
  279. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/io_utils.py +0 -0
  280. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/lifecycle.py +0 -0
  281. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/manifest.py +0 -0
  282. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/menu_keys.py +0 -0
  283. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/migration_lock.py +0 -0
  284. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/path_utils.py +0 -0
  285. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/proc.py +0 -0
  286. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/redact.py +0 -0
  287. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/refinement_facts.py +0 -0
  288. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/running_version.py +0 -0
  289. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/template_drift.py +0 -0
  290. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/toml_edit.py +0 -0
  291. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
  292. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli/utils/uvl_params.py +0 -0
  293. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  294. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  295. {splent_cli-1.41.0 → splent_cli-1.42.0}/src/splent_cli.egg-info/requires.txt +0 -0
  296. {splent_cli-1.41.0 → splent_cli-1.42.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.41.0
3
+ Version: 1.42.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
  License-Expression: LGPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.41.0"
7
+ version = "1.42.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"
@@ -1,3 +1,4 @@
1
+ import contextlib
1
2
  import logging
2
3
  import os
3
4
 
@@ -20,6 +21,27 @@ def _missing_module(exc: BaseException) -> str | None:
20
21
  return None
21
22
 
22
23
 
24
+ @contextlib.contextmanager
25
+ def _quiet_unless(debug: bool):
26
+ """The framework's logging muted while the app is built for discovery.
27
+
28
+ Discovery runs before every command, including the ones a product that
29
+ is not built yet (selected, not configured or derived) runs while
30
+ getting there. Building its app then fails, and the framework logged
31
+ that failure with a full traceback on every single command, although
32
+ nothing was wrong. The failure is expected there and handled, so the
33
+ framework's logging is muted for the attempt.
34
+ """
35
+ if debug:
36
+ yield
37
+ return
38
+ logging.disable(logging.CRITICAL)
39
+ try:
40
+ yield
41
+ finally:
42
+ logging.disable(logging.NOTSET)
43
+
44
+
23
45
  class SPLENTCLI(click.Group):
24
46
  """
25
47
  Main SPLENT CLI class.
@@ -27,6 +49,7 @@ class SPLENTCLI(click.Group):
27
49
  - Automatically injects the Flask app context for commands marked with `requires_app = True`.
28
50
  - Checks DB connectivity for commands marked with `requires_db = True`.
29
51
  - Discovers CLI commands contributed by features via ``app.extensions["splent_feature_commands"]``.
52
+ - Discovers the product's own commands, ``product:<name>`` (services/own_commands.py).
30
53
  - Displays commands grouped by category for a cleaner, more readable help output.
31
54
  """
32
55
 
@@ -49,44 +72,65 @@ class SPLENTCLI(click.Group):
49
72
  return self._feature_cmds_cache
50
73
 
51
74
  self._feature_cmds_cache: dict[str, click.BaseCommand] = {}
52
- # Discovery runs before every command, including the ones a product
53
- # that is not built yet (selected, not configured or derived) runs
54
- # while getting there. Building its app then fails, and the framework
55
- # logged that failure with a full traceback on every single command,
56
- # although nothing was wrong. The failure is expected here and
57
- # handled below, so the framework's logging is muted for the attempt.
58
75
  debug = bool(os.getenv("SPLENT_DEBUG"))
59
- if not debug:
60
- logging.disable(logging.CRITICAL)
61
76
  try:
62
- from splent_cli.utils.dynamic_imports import get_app
77
+ with _quiet_unless(debug):
78
+ from splent_cli.utils.dynamic_imports import get_app
63
79
 
64
- app = get_app()
65
- with app.app_context():
66
- registry = app.extensions.get("splent_feature_commands", {})
67
- for feature_short, commands in registry.items():
68
- cmd_names = ", ".join(c.name for c in commands if c.name)
69
- group = click.Group(
70
- name=f"feature:{feature_short}",
71
- help=f"Commands contributed by splent_feature_{feature_short}.",
72
- short_help=f"Subcommands: {cmd_names}"
73
- if cmd_names
74
- else f"Commands from splent_feature_{feature_short}.",
75
- )
76
- group.requires_app = True # type: ignore[attr-defined]
77
- for cmd in commands:
78
- group.add_command(cmd)
79
- self._feature_cmds_cache[group.name] = group
80
+ app = get_app()
81
+ with app.app_context():
82
+ registry = app.extensions.get("splent_feature_commands", {})
83
+ for feature_short, commands in registry.items():
84
+ cmd_names = ", ".join(c.name for c in commands if c.name)
85
+ group = click.Group(
86
+ name=f"feature:{feature_short}",
87
+ help=f"Commands contributed by splent_feature_{feature_short}.",
88
+ short_help=f"Subcommands: {cmd_names}"
89
+ if cmd_names
90
+ else f"Commands from splent_feature_{feature_short}.",
91
+ )
92
+ group.requires_app = True # type: ignore[attr-defined]
93
+ for cmd in commands:
94
+ group.add_command(cmd)
95
+ self._feature_cmds_cache[group.name] = group
80
96
  except Exception as e:
81
97
  if debug:
82
98
  click.secho(
83
99
  f" ⚠ Feature commands not loaded: {e}", fg="yellow", err=True
84
100
  )
85
- finally:
86
- if not debug:
87
- logging.disable(logging.NOTSET)
88
101
  return self._feature_cmds_cache
89
102
 
103
+ def load_own_commands(self):
104
+ """The product's own commands, ``product:<name>``, as an
105
+ :class:`~splent_cli.services.own_commands.OwnCommands`.
106
+
107
+ Found like a feature's: once the product's app is built, which
108
+ fails quietly here for a product that cannot be built yet. The
109
+ CLI's own product:* commands win over a product's.
110
+ """
111
+ if hasattr(self, "_own_cmds_cache"):
112
+ return self._own_cmds_cache
113
+
114
+ from splent_cli.services import own_commands
115
+
116
+ self._own_cmds_cache = own_commands.NONE
117
+ debug = bool(os.getenv("SPLENT_DEBUG"))
118
+ try:
119
+ with _quiet_unless(debug):
120
+ from splent_cli.utils.dynamic_imports import get_app
121
+
122
+ app = get_app()
123
+ with app.app_context():
124
+ self._own_cmds_cache = own_commands.collect(
125
+ os.getenv("SPLENT_APP"), set(self.commands)
126
+ )
127
+ except Exception as e:
128
+ if debug:
129
+ click.secho(
130
+ f" ⚠ Product commands not loaded: {e}", fg="yellow", err=True
131
+ )
132
+ return self._own_cmds_cache
133
+
90
134
  def get_command(self, ctx, cmd_name):
91
135
  # "feature:<short>" belongs to the feature called <short> whenever the
92
136
  # product installs one. Built-ins win everywhere else.
@@ -100,13 +144,19 @@ class SPLENTCLI(click.Group):
100
144
  feat_cmds = self._load_feature_commands()
101
145
  if cmd_name in feat_cmds:
102
146
  return feat_cmds[cmd_name]
103
- return super().get_command(ctx, cmd_name)
147
+ command = super().get_command(ctx, cmd_name)
148
+ # "product:<name>" is the product's only where the CLI has no
149
+ # command of that name, so asking for a built-in never imports it.
150
+ if command is None and cmd_name and cmd_name.startswith("product:"):
151
+ command = self.load_own_commands().commands.get(cmd_name)
152
+ return command
104
153
 
105
154
  def list_commands(self, ctx):
106
155
  builtin = super().list_commands(ctx)
107
156
  feat = sorted(self._load_feature_commands().keys())
157
+ own = sorted(self.load_own_commands().commands)
108
158
  # A name a feature claimed is listed once, as the feature's.
109
- return [name for name in builtin if name not in feat] + feat
159
+ return [name for name in builtin if name not in feat] + feat + own
110
160
 
111
161
  # ── App context injection ─────────────────────────────────────
112
162
 
@@ -132,9 +182,14 @@ class SPLENTCLI(click.Group):
132
182
  cmd_name = ctx.protected_args[0] if ctx.protected_args else None
133
183
  command = self.get_command(ctx, cmd_name)
134
184
 
135
- # A feature's own commands are only found by building the app, so
136
- # here they may be missing although the product has them.
137
- if command is None and cmd_name and cmd_name.startswith("feature:"):
185
+ # A feature's commands, and the product's own, are only found by
186
+ # building the app, so here they may be missing although the
187
+ # product has them.
188
+ if (
189
+ command is None
190
+ and cmd_name
191
+ and cmd_name.startswith(("feature:", "product:"))
192
+ ):
138
193
  self._delegate_to_the_product(ctx)
139
194
 
140
195
  if command and getattr(command, "requires_app", False):
@@ -169,6 +224,7 @@ class SPLENTCLI(click.Group):
169
224
  """Group SPLENT commands by category in the CLI help output."""
170
225
  all_cmds = self.list_commands(ctx)
171
226
  feat_cmds = self._load_feature_commands()
227
+ own_cmds = self.load_own_commands().commands
172
228
  groups = {
173
229
  "🌿 Feature Management": [
174
230
  cmd
@@ -176,7 +232,9 @@ class SPLENTCLI(click.Group):
176
232
  if cmd.startswith("feature:") and cmd not in feat_cmds
177
233
  ],
178
234
  "🏗️ Product Management": [
179
- cmd for cmd in all_cmds if cmd.startswith("product:")
235
+ cmd
236
+ for cmd in all_cmds
237
+ if cmd.startswith("product:") and cmd not in own_cmds
180
238
  ],
181
239
  "🧬 SPL & Variability": [
182
240
  cmd for cmd in all_cmds if cmd.startswith(("spl:", "uvl:"))
@@ -207,6 +265,7 @@ class SPLENTCLI(click.Group):
207
265
  if cmd in ("lint", "coverage", "selenium", "locust", "locust:stop")
208
266
  ],
209
267
  "🔌 Feature Commands": [cmd for cmd in all_cmds if cmd in feat_cmds],
268
+ "🔌 Product Commands": [cmd for cmd in all_cmds if cmd in own_cmds],
210
269
  }
211
270
  total = 0
212
271
  for title, cmds in groups.items():
@@ -150,7 +150,7 @@ def _workspace_owner() -> tuple[int, int] | None:
150
150
  return owner.st_uid, owner.st_gid
151
151
 
152
152
 
153
- def run_tests_in_web_container(product: str, argv: list[str]) -> int:
153
+ def run_tests_in_web_container(product: str, argv: list[str], env=()) -> int:
154
154
  """Run ``splent <argv>`` in PRODUCT's web container, as a test run.
155
155
 
156
156
  Returns its exit code, or 1 when the container is not running. The run
@@ -158,7 +158,8 @@ def run_tests_in_web_container(product: str, argv: list[str]) -> int:
158
158
  process does (see :func:`_tied_to_the_cli`), and runs as the workspace's
159
159
  owner (:func:`workspace_user`). feature:test runs there this way, and so
160
160
  do the product's own tests (product:test) and the release gate, which
161
- runs feature:test.
161
+ runs feature:test. ``env`` names more variables of this process's
162
+ environment for the run (product:test's [tool.splent.test] env).
162
163
  """
163
164
  name = web_container.name(product)
164
165
  if not _container_running(name):
@@ -180,6 +181,11 @@ def run_tests_in_web_container(product: str, argv: list[str]) -> int:
180
181
  value = os.environ.get(var)
181
182
  if value:
182
183
  passthrough += ["-e", f"{var}={value}"]
184
+ # By name alone: docker reads each value from this process's
185
+ # environment, so a password never shows in the exec's arguments.
186
+ for var in env:
187
+ if var in os.environ:
188
+ passthrough += ["-e", var]
183
189
  # The exec reads a pipe only this process writes to, so it sees the end
184
190
  # of its input exactly when this process ends, however it ends.
185
191
  read_end, write_end = os.pipe()
@@ -1,7 +1,9 @@
1
+ import os
2
+
1
3
  import click
2
4
  from flask import current_app
3
5
 
4
- from splent_cli.services import context
6
+ from splent_cli.services import context, own_commands
5
7
  from splent_cli.utils.decorators import requires_app
6
8
 
7
9
 
@@ -25,46 +27,20 @@ def _format_flags(cmd) -> str:
25
27
  return ", ".join(parts)
26
28
 
27
29
 
28
- @requires_app
29
- @click.command(
30
- "product:commands",
31
- short_help="List all CLI commands contributed by features.",
32
- )
33
- @context.requires_product
34
- def product_commands():
35
- """Show CLI commands registered by features in the active product."""
36
- registry = current_app.extensions.get("splent_feature_commands", {})
37
-
38
- if not registry:
39
- click.echo()
40
- click.secho(" No feature commands registered.", fg="yellow")
41
- click.echo()
42
- return
43
-
44
- # Build rows: (feature, invocation, flags, description)
45
- rows = []
46
- for feature_short, commands in sorted(registry.items()):
47
- for cmd in sorted(commands, key=lambda c: c.name):
48
- desc = cmd.get_short_help_str() or "—"
49
- invocation = f"feature:{feature_short} {cmd.name}"
50
- flags = _format_flags(cmd)
51
- rows.append((feature_short, invocation, flags, desc))
52
-
53
- # Column widths based on visible text
54
- col_feat = max(len(r[0]) for r in rows)
55
- col_feat = max(col_feat, len("Feature"))
56
- col_cmd = max(len(r[1]) for r in rows)
57
- col_cmd = max(col_cmd, len("Command"))
58
- col_flags = max(len(r[2]) for r in rows)
59
- col_flags = max(col_flags, len("Flags"))
30
+ def _table(title: str, owner: str, rows) -> None:
31
+ """Print ``rows`` of (owner, invocation, flags, description) under TITLE,
32
+ the owner named once per run of its rows."""
33
+ col_feat = max(max(len(r[0]) for r in rows), len(owner))
34
+ col_cmd = max(max(len(r[1]) for r in rows), len("Command"))
35
+ col_flags = max(max(len(r[2]) for r in rows), len("Flags"))
60
36
 
61
37
  click.echo()
62
- click.secho(" Feature Commands", bold=True, fg="cyan")
38
+ click.secho(f" {title}", bold=True, fg="cyan")
63
39
  click.echo()
64
40
 
65
41
  # Header
66
42
  click.echo(
67
- f" {'Feature':<{col_feat}} {'Command':<{col_cmd}} {'Flags':<{col_flags}} Description"
43
+ f" {owner:<{col_feat}} {'Command':<{col_cmd}} {'Flags':<{col_flags}} Description"
68
44
  )
69
45
  click.echo(f" {'-' * col_feat} {'-' * col_cmd} {'-' * col_flags} {'-' * 30}")
70
46
 
@@ -85,8 +61,66 @@ def product_commands():
85
61
  )
86
62
  prev_feat = feat
87
63
 
88
- click.echo()
89
- click.secho(f" {len(rows)} command(s) from {len(registry)} feature(s).", dim=True)
64
+
65
+ def _own_commands():
66
+ """The product's own commands, against the CLI's built-in names."""
67
+ from splent_cli.cli import cli
68
+
69
+ return own_commands.collect(os.getenv("SPLENT_APP"), set(cli.commands))
70
+
71
+
72
+ @requires_app
73
+ @click.command(
74
+ "product:commands",
75
+ short_help="List the CLI commands of the product's features and its own.",
76
+ )
77
+ @context.requires_product
78
+ def product_commands():
79
+ """Show the CLI commands the active product's features register, and the
80
+ product's own (product:<name>, from its commands module)."""
81
+ registry = current_app.extensions.get("splent_feature_commands", {})
82
+ own = _own_commands()
83
+ product = os.getenv("SPLENT_APP", "product")
84
+
85
+ if not registry and not own.commands and not own.shadowed and not own.error:
86
+ click.echo()
87
+ click.secho(" No feature or product commands registered.", fg="yellow")
88
+ click.echo()
89
+ return
90
+
91
+ # Build rows: (feature, invocation, flags, description)
92
+ rows = []
93
+ for feature_short, commands in sorted(registry.items()):
94
+ for cmd in sorted(commands, key=lambda c: c.name):
95
+ desc = cmd.get_short_help_str() or "—"
96
+ invocation = f"feature:{feature_short} {cmd.name}"
97
+ rows.append((feature_short, invocation, _format_flags(cmd), desc))
98
+ if rows:
99
+ _table("Feature Commands", "Feature", rows)
100
+ click.echo()
101
+ click.secho(
102
+ f" {len(rows)} command(s) from {len(registry)} feature(s).", dim=True
103
+ )
104
+
105
+ own_rows = [
106
+ (product, name, _format_flags(cmd), cmd.get_short_help_str() or "—")
107
+ for name, cmd in sorted(own.commands.items())
108
+ ]
109
+ if own_rows:
110
+ _table("Product Commands", "Product", own_rows)
111
+ click.echo()
112
+ click.secho(f" {len(own_rows)} command(s) of {product}'s own.", dim=True)
113
+ for name in own.shadowed:
114
+ click.secho(
115
+ f" ⚠ {product} has a command {name}, which is the CLI's own and "
116
+ "wins: rename it to reach it.",
117
+ fg="yellow",
118
+ )
119
+ if own.error:
120
+ click.secho(
121
+ f" ⚠ {product}'s own commands are not available: {own.error}",
122
+ fg="yellow",
123
+ )
90
124
  click.echo()
91
125
 
92
126
 
@@ -12,16 +12,26 @@ check the configuration the product is, such as the URLs it promises to keep
12
12
  serving, which no feature's tests can, so a product that ships them has them
13
13
  run wherever its features' tests run: here, in its release checks and in its
14
14
  CI. A product without them is tested as before.
15
+
16
+ Its own tests may need what only the caller has, such as the address of a
17
+ throwaway server the caller started for them. The product names those
18
+ variables in ``[tool.splent.test] env`` and product:test hands them over
19
+ from the caller's environment, into the web container too. It never starts
20
+ anything for them.
15
21
  """
16
22
 
17
23
  import os
24
+ import re
18
25
  import sys
19
26
  from pathlib import Path
20
27
 
21
28
  import click
22
29
 
23
30
  from splent_cli.services import context, web_container
24
- from splent_cli.utils.feature_utils import load_product_features
31
+ from splent_cli.utils.feature_utils import (
32
+ load_product_features,
33
+ load_product_pyproject,
34
+ )
25
35
  from splent_cli.utils.proc import run
26
36
 
27
37
  # Where a product keeps its own tests, and the files pytest collects there
@@ -29,6 +39,56 @@ from splent_cli.utils.proc import run
29
39
  OWN_TESTS_DIR = "tests"
30
40
  _OWN_TEST_PATTERNS = ("test_*.py", "*_test.py")
31
41
 
42
+ # An entry of [tool.splent.test] env: a variable's name, or a prefix of
43
+ # names ending in *.
44
+ _ENV_ENTRY = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\*?")
45
+ # Never handed over, whatever a pattern matches: the run sets SPLENT_APP and
46
+ # the like itself, and the tests use the web container's database settings,
47
+ # so a caller's MARIADB_DATABASE cannot point them at another database.
48
+ _NOT_HANDED = ("SPLENT_", "MARIADB_")
49
+
50
+
51
+ def declared_test_env(product_path) -> list[str]:
52
+ """What ``[tool.splent.test] env`` of the product names; [] without it.
53
+
54
+ Raises click.ClickException for an entry that is neither a variable's
55
+ name nor a prefix ending in ``*``, so a typo is not a test that quietly
56
+ goes without its server.
57
+ """
58
+ try:
59
+ data = load_product_pyproject(str(product_path))
60
+ except FileNotFoundError:
61
+ return []
62
+ entries = data.get("tool", {}).get("splent", {}).get("test", {}).get("env", [])
63
+ if not isinstance(entries, list):
64
+ raise click.ClickException(
65
+ "[tool.splent.test] env is a list of variable names, such as "
66
+ '["MY_TESTS_DB_HOST", "MY_TESTS_*"]'
67
+ )
68
+ for entry in entries:
69
+ if not isinstance(entry, str) or not _ENV_ENTRY.fullmatch(entry):
70
+ raise click.ClickException(
71
+ f"[tool.splent.test] env names {entry!r}, which is neither a "
72
+ "variable's name nor a prefix ending in *"
73
+ )
74
+ return entries
75
+
76
+
77
+ def handed_test_env(product_path, environ=None) -> list[str]:
78
+ """The variables of ``environ`` (this process's) that the product's own
79
+ tests are handed: the ones its ``[tool.splent.test] env`` names that are
80
+ set, but for SPLENT_* and MARIADB_*."""
81
+ environ = os.environ if environ is None else environ
82
+ entries = declared_test_env(product_path)
83
+ names = {e for e in entries if not e.endswith("*")}
84
+ prefixes = tuple(e[:-1] for e in entries if e.endswith("*"))
85
+ return sorted(
86
+ name
87
+ for name in environ
88
+ if (name in names or (prefixes and name.startswith(prefixes)))
89
+ and not name.startswith(_NOT_HANDED)
90
+ )
91
+
32
92
 
33
93
  def own_test_files(product_path) -> list[Path]:
34
94
  """The product's own test files, under ``<product>/tests``; [] without any."""
@@ -106,7 +166,8 @@ def _run_own_tests(product, verbose) -> None:
106
166
  from the CLI they run in the web container, as feature:test does, one
107
167
  test run at a time and stopped with the CLI. There, or in the CI job
108
168
  that stands in for it, they run against the test database and against
109
- the features as the product's app loads them.
169
+ the features as the product's app loads them, with the variables of
170
+ this environment the product declares in ``[tool.splent.test] env``.
110
171
  """
111
172
  from splent_cli.commands.feature.feature_test import (
112
173
  _validate_testing_environment,
@@ -122,7 +183,15 @@ def _run_own_tests(product, verbose) -> None:
122
183
  product = context.require_app()
123
184
  if not web_container.is_product_container():
124
185
  argv = ["product:test", "--own-tests"] + (["-v"] if verbose else [])
125
- raise SystemExit(run_tests_in_web_container(product, argv))
186
+ # Here the variables a product declares are handed over by name; in
187
+ # the container, and in the CI job, the whole environment is.
188
+ handed = handed_test_env(context.workspace() / product)
189
+ if handed:
190
+ click.secho(
191
+ f" handing the tests {', '.join(handed)} ([tool.splent.test] env)",
192
+ dim=True,
193
+ )
194
+ raise SystemExit(run_tests_in_web_container(product, argv, env=handed))
126
195
 
127
196
  _validate_testing_environment()
128
197
 
@@ -196,7 +265,8 @@ def product_test(
196
265
  run after the features', in the product's web container, against the
197
266
  features as its app loads them (the versions it pins, the working copy
198
267
  of an editable one), and only without a level flag. A product without a
199
- tests/ folder runs its features' tests alone.
268
+ tests/ folder runs its features' tests alone. The variables of your
269
+ environment the product names in [tool.splent.test] env reach them.
200
270
 
201
271
  \b
202
272
  Examples:
@@ -0,0 +1,76 @@
1
+ """A product's own CLI commands, ``splent product:<name>``.
2
+
3
+ A product declares them as a feature does: a ``commands`` module in its
4
+ package with a ``cli_commands`` list of Click commands. A feature's become
5
+ subcommands of ``feature:<short>``; a product's are commands of their own,
6
+ ``product:<name>``, since one product is active at a time. Like a
7
+ feature's, they are found once the product's app is built, and run in its
8
+ context.
9
+
10
+ The CLI's own ``product:*`` commands win. A product command named like one
11
+ of them cannot be reached, and product:commands says so: a CLI that ships a
12
+ new built-in never takes a product's command away unnoticed.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import importlib
18
+ from typing import NamedTuple
19
+
20
+ import click
21
+
22
+ PREFIX = "product:"
23
+ MODULE = "commands"
24
+
25
+
26
+ class OwnCommands(NamedTuple):
27
+ """What a product contributes: ``commands`` by the name they are run
28
+ with, ``shadowed`` the names a built-in took, and ``error`` why its
29
+ commands module could not be imported, None when it could."""
30
+
31
+ commands: dict[str, click.Command]
32
+ shadowed: list[str]
33
+ error: str | None
34
+
35
+
36
+ NONE = OwnCommands({}, [], None)
37
+
38
+
39
+ def declared(product: str) -> list[click.Command]:
40
+ """The Click commands ``<product>.commands`` lists in ``cli_commands``.
41
+
42
+ [] when the product has no such module. Whatever else importing it
43
+ raises is raised, so the caller can say why its commands are missing.
44
+ """
45
+ name = f"{product}.{MODULE}"
46
+ try:
47
+ module = importlib.import_module(name)
48
+ except ModuleNotFoundError as e:
49
+ if e.name == name:
50
+ return []
51
+ raise
52
+ commands = getattr(module, "cli_commands", None) or ()
53
+ return [command for command in commands if isinstance(command, click.Command)]
54
+
55
+
56
+ def collect(product: str | None, builtin) -> OwnCommands:
57
+ """The commands PRODUCT contributes, each under ``product:<its name>``,
58
+ but for those whose name is in ``builtin`` (the CLI's own)."""
59
+ if not product:
60
+ return NONE
61
+ try:
62
+ commands = declared(product)
63
+ except Exception as e:
64
+ return OwnCommands({}, [], f"{product}.{MODULE}: {e}")
65
+ found, shadowed = {}, []
66
+ for command in commands:
67
+ if not command.name:
68
+ continue
69
+ name = PREFIX + command.name
70
+ if name in builtin:
71
+ shadowed.append(name)
72
+ continue
73
+ # Run in the app's context, as a feature's (cli.py).
74
+ command.requires_app = True # type: ignore[attr-defined]
75
+ found[name] = command
76
+ return OwnCommands(found, shadowed, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.41.0
3
+ Version: 1.42.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
  License-Expression: LGPL-3.0-or-later
@@ -171,6 +171,7 @@ src/splent_cli/services/marketplace.py
171
171
  src/splent_cli/services/marketplace_api.py
172
172
  src/splent_cli/services/marketplace_url.py
173
173
  src/splent_cli/services/neutral_app.py
174
+ src/splent_cli/services/own_commands.py
174
175
  src/splent_cli/services/preflight.py
175
176
  src/splent_cli/services/pypi_quota.py
176
177
  src/splent_cli/services/registry.py
File without changes
File without changes
File without changes
File without changes