splent-cli 1.18.0__tar.gz → 1.19.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 (193) hide show
  1. {splent_cli-1.18.0/src/splent_cli.egg-info → splent_cli-1.19.0}/PKG-INFO +1 -1
  2. {splent_cli-1.18.0 → splent_cli-1.19.0}/pyproject.toml +1 -1
  3. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_contract.py +84 -0
  4. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_release.py +53 -10
  5. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_upgrade.py +29 -2
  6. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_build.py +28 -12
  7. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_env.py +109 -0
  8. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/compose.py +25 -10
  9. {splent_cli-1.18.0 → splent_cli-1.19.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  10. {splent_cli-1.18.0 → splent_cli-1.19.0}/LICENSE +0 -0
  11. {splent_cli-1.18.0 → splent_cli-1.19.0}/README.md +0 -0
  12. {splent_cli-1.18.0 → splent_cli-1.19.0}/setup.cfg +0 -0
  13. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/__init__.py +0 -0
  14. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/__main__.py +0 -0
  15. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/cli.py +0 -0
  16. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/__init__.py +0 -0
  17. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  18. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  19. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  20. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  21. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  22. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  23. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  24. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  25. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  26. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/__init__.py +0 -0
  27. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
  28. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  29. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  30. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_env.py +0 -0
  31. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_features.py +0 -0
  32. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_github.py +0 -0
  33. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  34. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_product.py +0 -0
  35. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  36. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  37. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/check/check_releases.py +0 -0
  38. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  39. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  40. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  41. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  42. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/command/__init__.py +0 -0
  43. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/command/command_create.py +0 -0
  44. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/coverage.py +0 -0
  45. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_console.py +0 -0
  46. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  47. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  48. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_reset.py +0 -0
  49. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  50. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  51. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  52. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_status.py +0 -0
  53. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  54. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/doctor.py +0 -0
  55. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/env/env_list.py +0 -0
  56. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/env/env_set.py +0 -0
  57. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/env/env_show.py +0 -0
  58. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/export/__init__.py +0 -0
  59. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  60. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  61. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  62. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  63. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  64. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  65. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  66. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  67. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  68. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  69. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  70. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  71. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  72. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  73. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  74. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  75. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  76. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  77. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  78. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
  79. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  80. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  81. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  82. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  83. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  84. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  85. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  86. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  87. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  88. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  89. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  90. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  91. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  92. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  93. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
  94. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  95. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  96. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  97. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  98. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/lint.py +0 -0
  99. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/locust.py +0 -0
  100. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/login.py +0 -0
  101. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
  102. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  103. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  104. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  105. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/__init__.py +0 -0
  106. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  107. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  108. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  109. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_config.py +0 -0
  110. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  111. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_console.py +0 -0
  112. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  113. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_create.py +0 -0
  114. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  115. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  116. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  117. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_down.py +0 -0
  118. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  119. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_list.py +0 -0
  120. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  121. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  122. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_port.py +0 -0
  123. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_release.py +0 -0
  124. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  125. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  126. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  127. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_run.py +0 -0
  128. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_select.py +0 -0
  129. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  130. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  131. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  132. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_test.py +0 -0
  133. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_up.py +0 -0
  134. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  135. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/release/__init__.py +0 -0
  136. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/release/release_core.py +0 -0
  137. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/release/release_resume.py +0 -0
  138. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/selenium.py +0 -0
  139. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  140. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  141. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  142. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  143. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  144. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  145. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  146. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  147. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  148. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  149. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  150. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_migrate_catalog.py +0 -0
  151. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  152. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  153. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  154. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  155. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  156. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  157. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  158. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/commands/version.py +0 -0
  159. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/__init__.py +0 -0
  160. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/context.py +0 -0
  161. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/credentials.py +0 -0
  162. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/index_refresh.py +0 -0
  163. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/marketplace.py +0 -0
  164. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/marketplace_api.py +0 -0
  165. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/marketplace_url.py +0 -0
  166. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/preflight.py +0 -0
  167. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/registry.py +0 -0
  168. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/release.py +0 -0
  169. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/release_gate.py +0 -0
  170. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/services/spl_store.py +0 -0
  171. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/__init__.py +0 -0
  172. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/archetype.py +0 -0
  173. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/cache_utils.py +0 -0
  174. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/command_loader.py +0 -0
  175. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  176. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/db_utils.py +0 -0
  177. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/decorators.py +0 -0
  178. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  179. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/feature_installer.py +0 -0
  180. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/feature_utils.py +0 -0
  181. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/git_url.py +0 -0
  182. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/integrity.py +0 -0
  183. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/io_utils.py +0 -0
  184. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/lifecycle.py +0 -0
  185. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/manifest.py +0 -0
  186. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/path_utils.py +0 -0
  187. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/proc.py +0 -0
  188. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli/utils/template_drift.py +0 -0
  189. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/SOURCES.txt +0 -0
  190. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  191. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  192. {splent_cli-1.18.0 → splent_cli-1.19.0}/src/splent_cli.egg-info/requires.txt +0 -0
  193. {splent_cli-1.18.0 → splent_cli-1.19.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.18.0
3
+ Version: 1.19.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.18.0"
7
+ version = "1.19.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"
@@ -13,6 +13,9 @@ Works with both editable features (no version) and versioned features (@v1.x.x).
13
13
  """
14
14
 
15
15
  import os
16
+ import re
17
+ import tomllib
18
+
16
19
  import click
17
20
  from pathlib import Path
18
21
 
@@ -337,6 +340,10 @@ def feature_contract(feature_ref, write):
337
340
 
338
341
  if not has_changes:
339
342
  click.secho(" ✅ Contract is already up to date.", fg="green")
343
+ # Reported here too: a settings gap has nothing to do with whether
344
+ # the contract changed, and an up to date contract is exactly when
345
+ # nobody would think to look.
346
+ _check_declared_config(cache_path, inferred)
340
347
  click.echo()
341
348
  return
342
349
 
@@ -347,6 +354,7 @@ def feature_contract(feature_ref, write):
347
354
  + click.style("--write", bold=True)
348
355
  + " to update pyproject.toml."
349
356
  )
357
+ _check_declared_config(cache_path, inferred)
350
358
  click.echo()
351
359
  return
352
360
 
@@ -357,7 +365,83 @@ def feature_contract(feature_ref, write):
357
365
  # Check if config.py needs updating
358
366
  _check_config_py(cache_path, ns, name, inferred)
359
367
 
368
+ # And whether the settings it reads are declared where a product looks
369
+ _check_declared_config(cache_path, inferred)
370
+
371
+ click.echo()
372
+
373
+
374
+ # Variables whose value is a credential. A default for one of these does not
375
+ # belong in a committed file, so they are expected to be absent from the
376
+ # declaration and are not reported as missing.
377
+ _SECRET = re.compile(r"(SECRET|PASSWORD|API_KEY|TOKEN|_KEY$)")
378
+
379
+ # Set for every product by the framework and the compose files. A feature
380
+ # that reads one is asking where it is running, not offering a setting, so
381
+ # declaring a default for it would be claiming something it does not own.
382
+ _FRAMEWORK_VARS = {
383
+ "FLASK_APP",
384
+ "PYTHONPATH",
385
+ "SPLENT_APP",
386
+ "SPLENT_ENV",
387
+ "SPLENT_HOST_PROJECT_DIR",
388
+ "SPLENT_NETWORK",
389
+ "WORKING_DIR",
390
+ }
391
+
392
+
393
+ def _check_declared_config(feature_path, inferred):
394
+ """Report settings the feature reads but declares nowhere.
395
+
396
+ A variable that appears only as an ``os.getenv`` fallback is invisible:
397
+ a product cannot discover the knob exists, and nothing tells a reader of
398
+ the pyproject what happens if it is left alone. ``[tool.splent.config]``
399
+ is where that is written down, whether or not the feature ships a
400
+ container.
401
+ """
402
+ env_vars = [
403
+ v
404
+ for v in inferred.get("env_vars", [])
405
+ if not _SECRET.search(v) and v not in _FRAMEWORK_VARS
406
+ ]
407
+ if not env_vars:
408
+ return
409
+
410
+ pyproject = feature_path / "pyproject.toml"
411
+ try:
412
+ with open(pyproject, "rb") as handle:
413
+ data = tomllib.load(handle)
414
+ except (OSError, tomllib.TOMLDecodeError):
415
+ return
416
+
417
+ declared = set(data.get("tool", {}).get("splent", {}).get("config", {}))
418
+ # A feature that ships a service may state a default next to its compose
419
+ # file instead, which is equally visible to product:env --merge.
420
+ env_example = feature_path / "docker" / ".env.example"
421
+ if env_example.exists():
422
+ declared |= {
423
+ line.split("=", 1)[0].strip()
424
+ for line in env_example.read_text(
425
+ encoding="utf-8", errors="replace"
426
+ ).splitlines()
427
+ if "=" in line and not line.strip().startswith("#")
428
+ }
429
+
430
+ missing = sorted(set(env_vars) - declared)
431
+ if not missing:
432
+ return
433
+
360
434
  click.echo()
435
+ click.secho(
436
+ f" ⚠ {len(missing)} setting(s) read but not declared in "
437
+ f"[tool.splent.config]: {', '.join(missing)}",
438
+ fg="yellow",
439
+ )
440
+ click.secho(
441
+ " Add one line per variable, the value being its default, so a\n"
442
+ " product can see the knob and override only what it decides.",
443
+ dim=True,
444
+ )
361
445
 
362
446
 
363
447
  def _check_config_py(feature_path, ns, name, inferred):
@@ -551,6 +551,49 @@ def infer_contract(
551
551
  }
552
552
 
553
553
 
554
+ def _sections_not_owned_by_the_contract(tail: str) -> str:
555
+ """The TOML sections below the contract that the contract did not write.
556
+
557
+ ``[tool.splent.contract]`` and its subtables are regenerated from source
558
+ and go; anything else a maintainer put there stays, with the comments
559
+ written above it, since a comment above a table is about that table.
560
+ """
561
+ chunks: list[list[str]] = []
562
+ lead: list[str] = []
563
+ current: list[str] | None = None
564
+
565
+ for line in tail.splitlines():
566
+ if line.startswith("["):
567
+ if current is not None:
568
+ chunks.append(current)
569
+ current = [*lead, line]
570
+ lead = []
571
+ elif current is None:
572
+ # Before the first table: comments introducing whatever follows.
573
+ lead.append(line)
574
+ elif line.strip().startswith("#") or not line.strip():
575
+ # Might introduce the next table rather than close this one, so
576
+ # it is held until the next line says which.
577
+ lead.append(line)
578
+ else:
579
+ current.extend(lead)
580
+ lead = []
581
+ current.append(line)
582
+
583
+ if current is not None:
584
+ chunks.append(current)
585
+
586
+ kept = []
587
+ for chunk in chunks:
588
+ header = next((line for line in chunk if line.startswith("[")), "")
589
+ if header.startswith("[tool.splent.contract"):
590
+ continue
591
+ body = "\n".join(chunk).strip()
592
+ if body:
593
+ kept.append(body)
594
+ return "\n\n".join(kept)
595
+
596
+
554
597
  def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> None:
555
598
  path = Path(pyproject_path)
556
599
  text = path.read_text()
@@ -596,20 +639,20 @@ def write_contract(pyproject_path: str, contract: dict, feature_name: str) -> No
596
639
  "requires_features_manual": sorted(set(manual_requires)),
597
640
  }
598
641
 
599
- # Preserve any refinement section that comes after the contract
600
- refinement_block = ""
601
- ref_match = re.search(
602
- r"((?:^# -- Refinement.*?\n)?^\[tool\.splent\.refinement\].*)",
603
- text,
604
- re.MULTILINE | re.DOTALL,
605
- )
606
- if ref_match:
607
- refinement_block = "\n" + ref_match.group(1).rstrip() + "\n"
608
-
609
642
  match = re.search(r"^# ── Feature Contract\b.*$", text, re.MULTILINE)
610
643
  if not match:
611
644
  match = re.search(r"^\[tool\.splent\.contract\b", text, re.MULTILINE)
645
+
646
+ # Whatever a maintainer wrote below the contract is theirs, and the
647
+ # contract is refreshed by truncating from its header and writing a new
648
+ # block: everything after it went with it. That is how
649
+ # [tool.splent.config] vanished from five features the moment they were
650
+ # released, silently taking every declared default with them.
651
+ refinement_block = ""
612
652
  if match:
653
+ preserved = _sections_not_owned_by_the_contract(text[match.start() :])
654
+ if preserved:
655
+ refinement_block = "\n" + preserved + "\n"
613
656
  text = text[: match.start()]
614
657
  # Normalize trailing whitespace on both paths so a first write and a
615
658
  # rewrite produce byte-identical output.
@@ -50,8 +50,13 @@ def _read_features(pyproject_path: Path) -> list[dict]:
50
50
  for ref in refs:
51
51
  if "/" in ref:
52
52
  ns_part, rest = ref.split("/", 1)
53
- ns_github = ns_part
54
53
  ns_fs = normalize_namespace(ns_part)
54
+ # A pyproject writes the namespace the way a Python package
55
+ # needs it, splent_io, and GitHub serves it as splent-io. Taking
56
+ # it verbatim asked the API about an organisation that does not
57
+ # exist, and every feature was then reported as already up to
58
+ # date.
59
+ ns_github = ns_fs.replace("_", "-")
55
60
  else:
56
61
  ns_github = "splent-io"
57
62
  ns_fs = "splent_io"
@@ -178,9 +183,13 @@ def feature_upgrade(feature_ref, yes):
178
183
  click.secho(" Checking latest versions on GitHub...", fg="bright_black")
179
184
 
180
185
  upgrades = []
186
+ unchecked = []
181
187
  for feat in features:
182
188
  latest = _latest_remote_version(feat["ns_github"], feat["name"], token)
183
189
  if not latest:
190
+ # Not the same as being up to date, and saying so would be a
191
+ # lie about the one thing this command is for.
192
+ unchecked.append(feat["name"])
184
193
  continue
185
194
 
186
195
  current = feat["version"]
@@ -197,9 +206,27 @@ def feature_upgrade(feature_ref, yes):
197
206
  if is_newer:
198
207
  upgrades.append({**feat, "latest": latest})
199
208
 
209
+ if unchecked:
210
+ click.echo()
211
+ click.secho(
212
+ f" ⚠ Could not check {len(unchecked)}: {', '.join(unchecked)}",
213
+ fg="yellow",
214
+ )
215
+ click.secho(
216
+ " Whatever is declared for these was left alone. They may or\n"
217
+ " may not have a newer version.",
218
+ dim=True,
219
+ )
220
+
200
221
  if not upgrades:
201
222
  click.echo()
202
- click.secho(" ✅ All features are already at the latest version.", fg="green")
223
+ if unchecked and len(unchecked) == len(features):
224
+ click.secho(" Nothing could be checked.", fg="yellow")
225
+ else:
226
+ click.secho(
227
+ " ✅ Every feature that could be checked is at its latest version.",
228
+ fg="green",
229
+ )
203
230
  click.echo()
204
231
  return
205
232
 
@@ -7,7 +7,11 @@ from splent_cli.services import context, compose
7
7
  from splent_cli.services.preflight import run_preflight
8
8
  from splent_cli.utils.feature_utils import read_features_from_data
9
9
  from splent_cli.utils.io_utils import load_toml
10
- from splent_cli.commands.product.product_env import _is_port_var
10
+ from splent_cli.commands.product.product_env import (
11
+ _declared_config,
12
+ _feature_pyproject,
13
+ _is_port_var,
14
+ )
11
15
 
12
16
 
13
17
  # ── User-tunable env vars ─────────────────────────────────────────────────────
@@ -415,19 +419,26 @@ def product_build(no_image, skip_preflight):
415
419
  seen_features.add(clean)
416
420
 
417
421
  f_docker = compose.feature_docker_dir(workspace, clean)
418
- if not os.path.isdir(f_docker):
419
- continue
422
+ if os.path.isdir(f_docker):
423
+ feature_env_file = (
424
+ os.path.join(f_docker, ".env.prod.example")
425
+ if os.path.isfile(os.path.join(f_docker, ".env.prod.example"))
426
+ else os.path.join(f_docker, ".env.example")
427
+ )
420
428
 
421
- feature_env_file = (
422
- os.path.join(f_docker, ".env.prod.example")
423
- if os.path.isfile(os.path.join(f_docker, ".env.prod.example"))
424
- else os.path.join(f_docker, ".env.example")
429
+ feature_env, feature_tunable = load_env_file_with_markers(feature_env_file)
430
+ feature_defaults = merge_env_dicts(feature_defaults, feature_env)
431
+ tunable_keys |= feature_tunable
432
+
433
+ # What the feature declares in its own pyproject, applied after its
434
+ # env example so the pyproject wins. Outside the isdir() guard on
435
+ # purpose: most features ship no docker/ at all, and those are the
436
+ # ones with nowhere else to state a default. Skipping them would put
437
+ # a deploy template together out of whatever config.py falls back to.
438
+ feature_defaults = merge_env_dicts(
439
+ feature_defaults, _declared_config(_feature_pyproject(workspace, clean))
425
440
  )
426
441
 
427
- feature_env, feature_tunable = load_env_file_with_markers(feature_env_file)
428
- feature_defaults = merge_env_dicts(feature_defaults, feature_env)
429
- tunable_keys |= feature_tunable
430
-
431
442
  # Apply the product port offset to feature port variables. Only to those:
432
443
  # the offset exists so two products sharing a feature default do not fight
433
444
  # over the same host port, and a port the product wrote itself is already
@@ -442,7 +453,12 @@ def product_build(no_image, skip_preflight):
442
453
  except ValueError:
443
454
  pass
444
455
 
445
- env_result = merge_env_dicts(feature_defaults, product_env)
456
+ # And what the product decides about those features, which beats their
457
+ # defaults and loses to the product's own env file, exactly as in
458
+ # product:env --merge. The two paths have to agree: a deploy template
459
+ # that disagreed with dev would only be found in production.
460
+ env_result = merge_env_dicts(feature_defaults, _declared_config(pyproject_path))
461
+ env_result = merge_env_dicts(env_result, product_env)
446
462
 
447
463
  # Resolve __PRODUCT__ placeholder
448
464
  for k, v in env_result.items():
@@ -43,6 +43,46 @@ def _parse_env_line(line: str):
43
43
  return key, value
44
44
 
45
45
 
46
+ def _declared_config(pyproject_path: str) -> dict:
47
+ """The ``[tool.splent.config]`` block of a pyproject, as strings.
48
+
49
+ A feature declares the variables it reads and what they default to; a
50
+ product declares the ones it decides differently. Both use the same
51
+ key, because they are the same kind of statement made by two different
52
+ owners, and the merge decides which wins.
53
+
54
+ Values are stringified because an env file has no types. A boolean
55
+ written as ``true`` in TOML reaches the environment as ``true``, which
56
+ is what every config.py in this workspace already parses.
57
+ """
58
+ try:
59
+ with open(pyproject_path, "rb") as handle:
60
+ data = tomllib.load(handle)
61
+ except (OSError, tomllib.TOMLDecodeError):
62
+ return {}
63
+
64
+ declared = data.get("tool", {}).get("splent", {}).get("config", {})
65
+ if not isinstance(declared, dict):
66
+ return {}
67
+
68
+ out = {}
69
+ for key, value in declared.items():
70
+ if isinstance(value, bool):
71
+ out[key] = "true" if value else "false"
72
+ elif isinstance(value, (int, float, str)):
73
+ out[key] = str(value)
74
+ elif isinstance(value, list):
75
+ # A list is the natural way to write something the reader of
76
+ # the .env will see comma separated, like a set of locales.
77
+ out[key] = ",".join(str(item) for item in value)
78
+ return out
79
+
80
+
81
+ def _feature_pyproject(workspace: str, clean_ref: str) -> str:
82
+ """Where a feature's own pyproject lives, editable or pinned."""
83
+ return os.path.join(compose.feature_dir(workspace, clean_ref), "pyproject.toml")
84
+
85
+
46
86
  def _is_port_var(key: str, value: str) -> bool:
47
87
  """Check if an env var looks like a port declaration.
48
88
 
@@ -231,6 +271,11 @@ def product_env(generate, merge, env_name, process_all):
231
271
  k, v = parsed
232
272
  merged[k] = v
233
273
 
274
+ # What the product's own env file states, captured before anything
275
+ # is merged into it. These keys are the product's last word: nothing
276
+ # declared in a pyproject, its own included, may quietly replace one.
277
+ stated_by_the_product = set(merged)
278
+
234
279
  # Always set SPLENT_ENV to match the current merge target
235
280
  merged["SPLENT_ENV"] = env_name
236
281
 
@@ -307,6 +352,59 @@ def product_env(generate, merge, env_name, process_all):
307
352
  # second product of the same line came up.
308
353
  merged["SPLENT_NETWORK"] = compose.network_name(product)
309
354
 
355
+ # ── What each side declares in its pyproject ─────────────────────
356
+ #
357
+ # A feature declares the variables it reads and their defaults; a
358
+ # product declares the ones it decides differently. Same key on both
359
+ # sides, because it is the same kind of statement made by two owners.
360
+ #
361
+ # This exists so a product's .env example can go back to being
362
+ # infrastructure. Feature settings used to be copied into it, which
363
+ # meant a product carried variables it had no opinion about, and a
364
+ # feature with no container had nowhere to declare a default at all,
365
+ # since .env.example lives under docker/.
366
+ #
367
+ # Precedence, weakest first:
368
+ # feature docker/.env.example (already merged above)
369
+ # feature [tool.splent.config]
370
+ # product [tool.splent.config]
371
+ # product .env.<env>.example (captured in stated_by_the_product)
372
+ #
373
+ # A feature that states a default in both places is saying the same
374
+ # thing twice; the pyproject wins because that is where a product's
375
+ # override sits, so the two can be read side by side.
376
+ declared_by_features = []
377
+ for feature in features:
378
+ clean_ref = compose.normalize_feature_ref(feature)
379
+ for key, value in _declared_config(
380
+ _feature_pyproject(workspace, clean_ref)
381
+ ).items():
382
+ if key in stated_by_the_product:
383
+ continue
384
+ if _is_port_var(key, value):
385
+ # Same offset the container defaults get: a port a
386
+ # feature declares is still a port two products would
387
+ # otherwise fight over.
388
+ try:
389
+ merged[key] = str(int(value) + port_offset)
390
+ port_adjusted.append(
391
+ (key, int(value), int(value) + port_offset)
392
+ )
393
+ except ValueError:
394
+ merged[key] = value
395
+ else:
396
+ merged[key] = value
397
+ declared_by_features.append(key)
398
+
399
+ product_declared = _declared_config(py_path)
400
+ for key, value in product_declared.items():
401
+ # Beats the feature's default, which is the point of writing it
402
+ # here, and loses to the product's own env file, which is the
403
+ # escape hatch for a setting an operator needs to force.
404
+ if key in stated_by_the_product:
405
+ continue
406
+ merged[key] = value
407
+
310
408
  # Replace __PRODUCT__ placeholder with the actual product name
311
409
  product_replaced = []
312
410
  for k, v in merged.items():
@@ -325,6 +423,17 @@ def product_env(generate, merge, env_name, process_all):
325
423
  + f"resolved __PRODUCT__ in {len(product_replaced)} variable(s) → {product}"
326
424
  )
327
425
 
426
+ if declared_by_features:
427
+ click.echo(
428
+ click.style(" declared ", dim=True)
429
+ + f"{len(declared_by_features)} default(s) from feature pyprojects"
430
+ )
431
+ if product_declared:
432
+ click.echo(
433
+ click.style(" product ", dim=True)
434
+ + f"{len(product_declared)} value(s) this product decides"
435
+ )
436
+
328
437
  if port_adjusted:
329
438
  click.echo(
330
439
  click.style(" ports ", dim=True)
@@ -437,24 +437,39 @@ def resolve_file(product_path: str, env: str) -> str | None:
437
437
  return None
438
438
 
439
439
 
440
- def feature_docker_dir(workspace: str, feature: str) -> str:
441
- """Return the docker/ directory for a feature.
442
-
443
- Accepts refs like 'splent_io/splent_feature_redis@v1.5.4' or bare 'splent_feature_redis'.
444
- Checks workspace root first (editable features), then falls back
445
- to the .splent_cache (pinned features).
440
+ def feature_dir(workspace: str, feature: str) -> str:
441
+ """Return the root directory of a feature: where its pyproject lives.
442
+
443
+ Accepts refs like 'splent_io/splent_feature_redis@v1.5.4' or bare
444
+ 'splent_feature_redis'. Checks the workspace root first (editable
445
+ features), then falls back to the .splent_cache (pinned features).
446
+
447
+ Resolution is by the feature's own directory rather than by any file
448
+ inside it, so a feature that ships no docker/ still resolves to its
449
+ editable copy. Keying on docker/ meant a feature with no container
450
+ silently resolved to a stale pinned copy in the cache, and read that
451
+ copy's settings instead of the ones being edited.
446
452
  """
447
453
  # Extract bare name (strip org prefix and version)
448
454
  name = feature.split("/")[-1] if "/" in feature else feature
449
455
  bare_name = name.split("@")[0]
450
456
 
451
457
  # Editable feature at workspace root
452
- root_docker = os.path.join(workspace, bare_name, "docker")
453
- if os.path.isdir(root_docker):
454
- return root_docker
458
+ root = os.path.join(workspace, bare_name)
459
+ if os.path.isdir(root):
460
+ return root
455
461
 
456
462
  # Pinned feature in cache (needs full ref with org and version)
457
- return os.path.join(workspace, ".splent_cache", "features", feature, "docker")
463
+ return os.path.join(workspace, ".splent_cache", "features", feature)
464
+
465
+
466
+ def feature_docker_dir(workspace: str, feature: str) -> str:
467
+ """Return the docker/ directory for a feature.
468
+
469
+ The directory need not exist: only features that bring a container
470
+ have one, and callers check before reading.
471
+ """
472
+ return os.path.join(feature_dir(workspace, feature), "docker")
458
473
 
459
474
 
460
475
  def normalize_feature_ref(feat: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.18.0
3
+ Version: 1.19.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
File without changes
File without changes
File without changes