splent-cli 1.28.1__tar.gz → 1.28.2__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 (200) hide show
  1. {splent_cli-1.28.1/src/splent_cli.egg-info → splent_cli-1.28.2}/PKG-INFO +1 -1
  2. {splent_cli-1.28.1 → splent_cli-1.28.2}/pyproject.toml +1 -1
  3. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_build.py +52 -4
  4. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_deploy.py +193 -52
  5. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_derive.py +10 -2
  6. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_env.py +17 -3
  7. {splent_cli-1.28.1 → splent_cli-1.28.2/src/splent_cli.egg-info}/PKG-INFO +1 -1
  8. {splent_cli-1.28.1 → splent_cli-1.28.2}/LICENSE +0 -0
  9. {splent_cli-1.28.1 → splent_cli-1.28.2}/README.md +0 -0
  10. {splent_cli-1.28.1 → splent_cli-1.28.2}/setup.cfg +0 -0
  11. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/__init__.py +0 -0
  12. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/__main__.py +0 -0
  13. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/cli.py +0 -0
  14. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/__init__.py +0 -0
  15. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/__init__.py +0 -0
  16. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  17. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  18. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  19. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  20. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_size.py +0 -0
  21. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_status.py +0 -0
  22. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  23. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  24. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/__init__.py +0 -0
  25. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_contracts.py +0 -0
  26. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_deps.py +0 -0
  27. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_docker.py +0 -0
  28. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_env.py +0 -0
  29. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_features.py +0 -0
  30. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_github.py +0 -0
  31. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_infra.py +0 -0
  32. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_product.py +0 -0
  33. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_pypi.py +0 -0
  34. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  35. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/check/check_releases.py +0 -0
  36. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/clear/__init__.py +0 -0
  37. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/clear/clear_build.py +0 -0
  38. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/clear/clear_log.py +0 -0
  39. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  40. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/command/__init__.py +0 -0
  41. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/command/command_create.py +0 -0
  42. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/coverage.py +0 -0
  43. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_console.py +0 -0
  44. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_dump.py +0 -0
  45. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_migrate.py +0 -0
  46. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_reset.py +0 -0
  47. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_restore.py +0 -0
  48. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_rollback.py +0 -0
  49. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_seed.py +0 -0
  50. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_status.py +0 -0
  51. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  52. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/doctor.py +0 -0
  53. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/env/env_list.py +0 -0
  54. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/env/env_set.py +0 -0
  55. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/env/env_show.py +0 -0
  56. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/export/__init__.py +0 -0
  57. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/export/export_puml.py +0 -0
  58. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_add.py +0 -0
  59. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  60. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  61. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  62. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  63. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  64. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  65. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_create.py +0 -0
  66. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  67. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  68. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  69. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  70. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_env.py +0 -0
  71. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  72. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_git.py +0 -0
  73. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  74. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  75. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  76. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  77. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_info.py +0 -0
  78. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  79. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_install.py +0 -0
  80. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_list.py +0 -0
  81. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_order.py +0 -0
  82. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  83. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  84. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  85. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  86. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  87. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_release.py +0 -0
  88. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  89. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  90. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_search.py +0 -0
  91. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_status.py +0 -0
  92. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  93. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_test.py +0 -0
  94. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  95. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  96. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  97. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  98. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  99. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/lint.py +0 -0
  100. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/locust.py +0 -0
  101. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/marketplace/login.py +0 -0
  102. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/marketplace/logout.py +0 -0
  103. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  104. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  105. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  106. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/__init__.py +0 -0
  107. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  108. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_clean.py +0 -0
  109. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_commands.py +0 -0
  110. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_config.py +0 -0
  111. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_configure.py +0 -0
  112. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_console.py +0 -0
  113. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_containers.py +0 -0
  114. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_create.py +0 -0
  115. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_deselect.py +0 -0
  116. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_down.py +0 -0
  117. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_drift.py +0 -0
  118. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_list.py +0 -0
  119. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_logs.py +0 -0
  120. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_missing.py +0 -0
  121. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_port.py +0 -0
  122. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_release.py +0 -0
  123. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_resolve.py +0 -0
  124. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_restart.py +0 -0
  125. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_routes.py +0 -0
  126. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_run.py +0 -0
  127. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_select.py +0 -0
  128. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_shell.py +0 -0
  129. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_signals.py +0 -0
  130. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  131. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_test.py +0 -0
  132. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_up.py +0 -0
  133. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/product/product_validate.py +0 -0
  134. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/release/__init__.py +0 -0
  135. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/release/release_core.py +0 -0
  136. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/release/release_resume.py +0 -0
  137. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/selenium.py +0 -0
  138. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/__init__.py +0 -0
  139. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  140. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  141. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  142. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  143. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_create.py +0 -0
  144. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  145. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_features.py +0 -0
  146. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  147. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_info.py +0 -0
  148. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_list.py +0 -0
  149. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  150. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  151. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  152. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  153. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  154. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/tokens_setup.py +0 -0
  155. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  156. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/commands/version.py +0 -0
  157. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/__init__.py +0 -0
  158. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/compose.py +0 -0
  159. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/context.py +0 -0
  160. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/credentials.py +0 -0
  161. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/index_refresh.py +0 -0
  162. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/marketplace.py +0 -0
  163. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/marketplace_api.py +0 -0
  164. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/marketplace_url.py +0 -0
  165. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/preflight.py +0 -0
  166. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/pypi_quota.py +0 -0
  167. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/registry.py +0 -0
  168. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/release.py +0 -0
  169. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/release_dry_run.py +0 -0
  170. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/release_gate.py +0 -0
  171. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/services/spl_store.py +0 -0
  172. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/__init__.py +0 -0
  173. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/archetype.py +0 -0
  174. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/cache_utils.py +0 -0
  175. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/command_loader.py +0 -0
  176. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/contract_freshness.py +0 -0
  177. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/db_client.py +0 -0
  178. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/db_utils.py +0 -0
  179. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/decorators.py +0 -0
  180. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/dynamic_imports.py +0 -0
  181. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/feature_installer.py +0 -0
  182. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/feature_utils.py +0 -0
  183. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/forge.py +0 -0
  184. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/git_url.py +0 -0
  185. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/integrity.py +0 -0
  186. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/io_utils.py +0 -0
  187. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/lifecycle.py +0 -0
  188. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/manifest.py +0 -0
  189. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/path_utils.py +0 -0
  190. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/proc.py +0 -0
  191. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/redact.py +0 -0
  192. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/refinement_facts.py +0 -0
  193. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/template_drift.py +0 -0
  194. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/toml_edit.py +0 -0
  195. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli/utils/upgrade_plan.py +0 -0
  196. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli.egg-info/SOURCES.txt +0 -0
  197. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  198. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli.egg-info/entry_points.txt +0 -0
  199. {splent_cli-1.28.1 → splent_cli-1.28.2}/src/splent_cli.egg-info/requires.txt +0 -0
  200. {splent_cli-1.28.1 → splent_cli-1.28.2}/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.28.1
3
+ Version: 1.28.2
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.28.1"
7
+ version = "1.28.2"
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"
@@ -937,6 +937,37 @@ def _collect_feature_assets(workspace, declared_features, docker_path, env="prod
937
937
  return copied
938
938
 
939
939
 
940
+ def unresolved_pins(
941
+ workspace: str, product: str, features: Sequence[str]
942
+ ) -> list[tuple[str, str]]:
943
+ """Pinned features this workspace has not resolved, each with the reason.
944
+
945
+ product:build reads every feature's env file and [tool.splent.config]
946
+ from the release the product pins, and copies the product's features/
947
+ links into the image. Both are product:resolve's work. Without it the
948
+ build used to carry on with whatever copy of the feature sat at the
949
+ workspace root, usually an old one: the .env.deploy.example lost every
950
+ setting the old copy did not declare, product:deploy then dropped them
951
+ from .env.deploy, and the image shipped links naming the old versions.
952
+ Editable features (no version) are not pinned and are not checked.
953
+ """
954
+ problems = []
955
+ for feat in features:
956
+ clean = compose.normalize_feature_ref(feat)
957
+ org_safe, _, name_ver = clean.partition("/")
958
+ if "@" not in name_ver:
959
+ continue
960
+ cached = os.path.join(
961
+ workspace, ".splent_cache", "features", org_safe, name_ver
962
+ )
963
+ linked = os.path.join(workspace, product, "features", org_safe, name_ver)
964
+ if not os.path.isdir(cached):
965
+ problems.append((name_ver, "not in .splent_cache"))
966
+ elif not os.path.isdir(linked):
967
+ problems.append((name_ver, f"not linked in {product}/features/"))
968
+ return problems
969
+
970
+
940
971
  @click.command(
941
972
  "product:build",
942
973
  short_help="Build deployment artifacts: env, compose, and Docker image.",
@@ -961,6 +992,27 @@ def product_build(no_image, skip_preflight):
961
992
 
962
993
  click.echo(f"🏗️ Building deployment artifacts for product: {product}\n")
963
994
 
995
+ # Read declared features from pyproject.toml (not glob)
996
+ pydata = load_toml(pyproject_path, what="pyproject.toml")
997
+ declared_features = read_features_from_data(pydata, "prod")
998
+
999
+ # ---------------------------------------------------------
1000
+ # The releases the product pins, on disk
1001
+ # ---------------------------------------------------------
1002
+ unresolved = unresolved_pins(workspace, product, declared_features)
1003
+ if unresolved:
1004
+ click.secho(
1005
+ " The build reads the releases pyproject.toml pins, and these are "
1006
+ "not resolved in this workspace.",
1007
+ fg="red",
1008
+ )
1009
+ width = max(len(name) for name, _ in unresolved)
1010
+ for name, why in unresolved:
1011
+ click.echo(f" {name.ljust(width)} {why}")
1012
+ click.echo()
1013
+ click.echo(" Run splent product:resolve and build again.")
1014
+ raise SystemExit(1)
1015
+
964
1016
  # ---------------------------------------------------------
965
1017
  # Pre-flight checks (UVL + feature contracts)
966
1018
  # ---------------------------------------------------------
@@ -987,10 +1039,6 @@ def product_build(no_image, skip_preflight):
987
1039
  # unnamed courses with no categories in production.
988
1040
  product_env, tunable_keys = load_env_file_with_markers(product_env_file)
989
1041
 
990
- # Read declared features from pyproject.toml (not glob)
991
- pydata = load_toml(pyproject_path, what="pyproject.toml")
992
- declared_features = read_features_from_data(pydata, "prod")
993
-
994
1042
  # Unversioned features, and what actually happens to them.
995
1043
  #
996
1044
  # This warning used to say the build would fail. It does not, and that is
@@ -1,15 +1,17 @@
1
1
  import os
2
2
  import re
3
3
  import subprocess
4
+ from dataclasses import dataclass, field
4
5
 
5
6
  import click
6
7
 
7
- from splent_cli.utils.io_utils import atomic_write
8
+ from splent_cli.utils.io_utils import atomic_write, env_line
8
9
  import yaml
9
10
  from splent_cli.services import compose, context
10
11
  from splent_cli.commands.product.product_build import (
11
12
  OPTIONAL_DEPLOY_KEYS,
12
13
  host_docker_dir_env,
14
+ is_user_tunable_marker,
13
15
  load_env_file_with_markers,
14
16
  render_deploy_env,
15
17
  )
@@ -45,60 +47,205 @@ def suggest_value(key: str, product: str) -> str | None:
45
47
  ENV_DEPLOY_MODE = 0o600
46
48
 
47
49
 
50
+ #: The template product:deploy merged into .env.deploy last time, kept next to
51
+ #: it. Comparing against it is how a deploy tells a value the operator edited
52
+ #: from one the template changed: without it, both look like "the file differs
53
+ #: from the template". Ignored by git and by the image like every .env.*.
54
+ ENV_DEPLOY_BASE_SUFFIX = ".base"
55
+
56
+ #: Keys the CLI derives for the product and the operator has no reason to edit.
57
+ #: Without a recorded base they still follow the template, so an upgrade that
58
+ #: renames one (the database host, the network) reaches deployments made
59
+ #: before the base existed.
60
+ _DERIVED_KEYS = frozenset({"SPLENT_ENV", "SPLENT_NETWORK"})
61
+
62
+
63
+ @dataclass
64
+ class TemplateSync:
65
+ """What merging the template into an operator's env file did."""
66
+
67
+ env: dict
68
+ #: Keys the template changed and the operator had left alone.
69
+ updated: list = field(default_factory=list)
70
+ #: Keys the operator set that the template now sets differently.
71
+ kept: list = field(default_factory=list)
72
+ #: Keys the operator file had and the template no longer names.
73
+ dropped: list = field(default_factory=list)
74
+ #: User-tunable keys the operator deleted, which stay deleted.
75
+ deleted_tunables: set = field(default_factory=set)
76
+
77
+
78
+ def derived_keys(product: str) -> frozenset:
79
+ """The keys the CLI writes into every product's production template.
80
+
81
+ Read from the CLI's own template rather than listed here, so a key the
82
+ template gains is covered too. Secrets (<SET>) and user-tunable keys are
83
+ the operator's and are left out.
84
+ """
85
+ from splent_cli.utils.template_drift import product_ctx, render_template
86
+
87
+ keys = set(_DERIVED_KEYS)
88
+ text = render_template("product/product_.env.prod.example.j2", product_ctx(product))
89
+ tunable_next = False
90
+ for line in text.splitlines():
91
+ stripped = line.strip()
92
+ if is_user_tunable_marker(stripped):
93
+ tunable_next = True
94
+ continue
95
+ if not stripped or stripped.startswith("#") or "=" not in stripped:
96
+ continue
97
+ key, _, value = stripped.partition("=")
98
+ if not tunable_next and value.strip() != "<SET>":
99
+ keys.add(key.strip())
100
+ tunable_next = False
101
+ return frozenset(keys)
102
+
103
+
104
+ def base_path(env_path: str) -> str:
105
+ """Where the template behind ``env_path`` is recorded."""
106
+ return env_path + ENV_DEPLOY_BASE_SUFFIX
107
+
108
+
109
+ def read_base(env_path: str) -> dict | None:
110
+ """The template last merged into ``env_path``, or None if never recorded."""
111
+ path = base_path(env_path)
112
+ if not os.path.isfile(path):
113
+ return None
114
+ values, _ = load_env_file_with_markers(path)
115
+ return values
116
+
117
+
118
+ def write_base(env_path: str, template_vars: dict) -> None:
119
+ """Record the template just merged into ``env_path`` for the next merge."""
120
+ atomic_write(
121
+ base_path(env_path),
122
+ "".join(env_line(k, v) for k, v in template_vars.items()),
123
+ dir_owner=True,
124
+ mode=ENV_DEPLOY_MODE,
125
+ force_mode=True,
126
+ )
127
+
128
+
48
129
  def sync_with_template(
49
- example_vars: dict, tunable_keys: set, existing_vars: dict
50
- ) -> tuple[dict, list, set]:
130
+ example_vars: dict,
131
+ tunable_keys: set,
132
+ existing_vars: dict,
133
+ base_vars: dict | None = None,
134
+ derived: frozenset = frozenset(),
135
+ ) -> TemplateSync:
51
136
  """Bring an operator's env file up to date with its template.
52
137
 
53
- The template is the source of truth for every infrastructure variable.
54
- Two kinds of value belong to the operator instead and are never
55
- overwritten:
56
- * secrets and other keys the template ships as <SET>, once filled in;
57
- * keys the template marks with a "# user-tunable" comment, such as the
58
- interface a port binds to. A marked key the operator deleted stays
59
- deleted, so removing it to fall back to the compose default works.
60
-
61
- Returns ``(env_vars, updated_keys, deleted_tunables)``. product:deploy and
62
- product:env --merge --prod both write .env.deploy through this, so one
63
- cannot undo what the other kept.
138
+ A three-way merge. ``base_vars`` is the template as it was when the file
139
+ was last merged, so for every key that differs from the template now:
140
+
141
+ * the operator's value equals the old template: the operator never
142
+ touched it, the template moved, and the new template value is taken;
143
+ * otherwise the operator changed it and their value is kept.
144
+
145
+ Before this the template won every disagreement except secrets and
146
+ user-tunable keys, so a setting the operator turned on (publishing in the
147
+ marketplace, an API key the template ships empty) was silently undone on
148
+ every deploy.
149
+
150
+ With no recorded base (a file merged by an older CLI) nothing tells the
151
+ two apart, so the operator's value is kept and reported, except for the
152
+ ``derived`` keys the CLI computes for the product, which follow the
153
+ template as they always did.
154
+
155
+ Secrets the template ships as <SET> and user-tunable keys are the
156
+ operator's in every case, and a tunable the operator deleted stays
157
+ deleted. Keys the template no longer names are dropped and reported.
158
+
159
+ product:deploy and product:env --merge --prod both write .env.deploy
160
+ through this, so one cannot undo what the other kept.
64
161
  """
162
+ result = TemplateSync(env={})
65
163
  # Keys the operator removed on purpose. Only meaningful for tunables: a
66
- # template-owned key that went missing is still restored below.
67
- deleted_tunables = {
164
+ # template-owned key that went missing is restored below.
165
+ result.deleted_tunables = {
68
166
  k for k in tunable_keys if k in example_vars and k not in existing_vars
69
167
  }
70
168
 
71
- env_vars = {}
72
- updated = []
73
169
  for k, v in example_vars.items():
74
- if k in deleted_tunables:
170
+ if k in result.deleted_tunables:
75
171
  # The operator deleted this tunable. Leave it out and let the
76
172
  # compose default apply.
77
173
  continue
78
- elif k in tunable_keys and k in existing_vars:
79
- # Operator-owned value — keep it exactly as they wrote it.
80
- env_vars[k] = existing_vars[k]
81
- elif (
82
- v.strip() == "<SET>"
83
- and k in existing_vars
84
- and existing_vars[k].strip() != "<SET>"
85
- ):
86
- # User already configured this — keep their value
87
- env_vars[k] = existing_vars[k]
88
- elif k in existing_vars and existing_vars[k] != v:
89
- # Infrastructure value changed in template — update it
90
- env_vars[k] = v
91
- updated.append(k)
174
+ if k not in existing_vars or existing_vars[k] == v:
175
+ result.env[k] = v
176
+ continue
177
+ mine = existing_vars[k]
178
+ if k in tunable_keys:
179
+ # Operator-owned value, kept exactly as they wrote it.
180
+ result.env[k] = mine
181
+ elif v.strip() == "<SET>":
182
+ # A secret, filled in unless the file still says <SET>.
183
+ result.env[k] = v if mine.strip() == "<SET>" else mine
184
+ elif base_vars is not None and k in base_vars:
185
+ if mine == base_vars[k]:
186
+ result.env[k] = v
187
+ result.updated.append(k)
188
+ else:
189
+ result.env[k] = mine
190
+ if base_vars[k] != v:
191
+ result.kept.append(k)
192
+ elif k in derived:
193
+ result.env[k] = v
194
+ result.updated.append(k)
92
195
  else:
93
- env_vars[k] = v
196
+ result.env[k] = mine
197
+ result.kept.append(k)
198
+
94
199
  # Optional operator settings (GUNICORN_WORKERS and the like) are not
95
200
  # live lines in any template, so the loop above never sees them. One the
96
201
  # operator set is theirs and stays, or uncommenting it in .env.deploy
97
202
  # would last until the next deploy.
98
203
  for k in sorted(OPTIONAL_DEPLOY_KEYS):
99
- if k in existing_vars and k not in env_vars and k not in deleted_tunables:
100
- env_vars[k] = existing_vars[k]
101
- return env_vars, updated, deleted_tunables
204
+ if (
205
+ k in existing_vars
206
+ and k not in result.env
207
+ and k not in result.deleted_tunables
208
+ ):
209
+ result.env[k] = existing_vars[k]
210
+
211
+ result.dropped = [
212
+ k
213
+ for k in existing_vars
214
+ if k not in result.env and k not in result.deleted_tunables
215
+ ]
216
+ return result
217
+
218
+
219
+ def report_sync(sync: TemplateSync, env_file: str) -> None:
220
+ """Say what merging the template changed, by key name only.
221
+
222
+ Values stay out of the output: half of them are passwords and keys.
223
+ """
224
+
225
+ def say(text: str) -> None:
226
+ click.echo(click.style(" env ", dim=True) + text)
227
+
228
+ if sync.updated:
229
+ say(
230
+ f"updated {len(sync.updated)} variable(s) from the template: "
231
+ + ", ".join(sync.updated)
232
+ )
233
+ if sync.kept:
234
+ say(
235
+ f"kept your value for {len(sync.kept)} variable(s) the template sets "
236
+ f"differently: " + ", ".join(sync.kept)
237
+ )
238
+ say(f" delete a line from {env_file} to take the template's value")
239
+ if sync.dropped:
240
+ say(
241
+ f"dropped {len(sync.dropped)} variable(s) the template no longer "
242
+ f"has: " + ", ".join(sync.dropped)
243
+ )
244
+ if sync.deleted_tunables:
245
+ say(
246
+ f"keeping {len(sync.deleted_tunables)} removed variable(s) out: "
247
+ + ", ".join(sorted(sync.deleted_tunables))
248
+ )
102
249
 
103
250
 
104
251
  # Env vars whose value is a directory the running app writes to. Their contents
@@ -266,22 +413,15 @@ def product_deploy(down, ci):
266
413
  # ---------------------------------------------------------
267
414
  existing_vars, _ = load_env_file_with_markers(env_path)
268
415
  example_vars, tunable_keys = load_env_file_with_markers(env_example_path)
269
- env_vars, updated, deleted_tunables = sync_with_template(
270
- example_vars, tunable_keys, existing_vars
416
+ sync = sync_with_template(
417
+ example_vars,
418
+ tunable_keys,
419
+ existing_vars,
420
+ base_vars=read_base(env_path),
421
+ derived=derived_keys(product),
271
422
  )
272
-
273
- if updated:
274
- click.echo(
275
- click.style(" env ", dim=True)
276
- + f"updated {len(updated)} variable(s) from template"
277
- )
278
-
279
- if deleted_tunables:
280
- click.echo(
281
- click.style(" env ", dim=True)
282
- + f"keeping {len(deleted_tunables)} removed variable(s) out: "
283
- + ", ".join(sorted(deleted_tunables))
284
- )
423
+ env_vars = sync.env
424
+ report_sync(sync, "docker/.env.deploy")
285
425
 
286
426
  # ---------------------------------------------------------
287
427
  # Ask interactively for missing (<SET>) values
@@ -345,6 +485,7 @@ def product_deploy(down, ci):
345
485
  mode=ENV_DEPLOY_MODE,
346
486
  force_mode=True,
347
487
  )
488
+ write_base(env_path, example_vars)
348
489
 
349
490
  # ---------------------------------------------------------
350
491
  # Say when the app will not believe its proxy
@@ -183,8 +183,9 @@ def product_derive(mode):
183
183
 
184
184
  \b
185
185
  --prod runs (after pre-flight):
186
- 1. product:build merge env + compose + build Docker image
187
- 2. product:deploy start production containers
186
+ 1. product:resolve clone the pinned releases and link them
187
+ 2. product:build merge env + compose + build Docker image
188
+ 3. product:deploy start production containers
188
189
 
189
190
  \b
190
191
  The pipeline stops at the first step that fails.
@@ -354,6 +355,13 @@ def product_derive(mode):
354
355
  step(product_port, env_flag="dev")
355
356
 
356
357
  elif mode == "prod":
358
+ # The build reads the pinned releases from the cache and refuses
359
+ # without them, so resolve them first, as the dev path does.
360
+ click.echo(
361
+ click.style(" sync ", dim=True) + "resolving the pinned features..."
362
+ )
363
+ step(product_sync, force=False, mode="prod", strict=True)
364
+
357
365
  click.echo(" build merging env + compose + Docker image")
358
366
  step(product_build, no_image=False, skip_preflight=True)
359
367
 
@@ -378,16 +378,29 @@ def product_env(generate, merge, env_name, process_all):
378
378
  if env_name == "prod":
379
379
  # .env.deploy is the operator's file: it holds the passwords and
380
380
  # the SECRET_KEY the running database was created with. The same
381
- # rule product:deploy uses keeps every <SET> the operator filled
382
- # in and every user-tunable value; the rest follows the template.
381
+ # three-way merge product:deploy uses keeps every value the
382
+ # operator set and takes every one the template changed.
383
383
  from splent_cli.commands.product.product_deploy import (
384
384
  ENV_DEPLOY_MODE,
385
+ derived_keys,
386
+ read_base,
387
+ report_sync,
385
388
  sync_with_template,
389
+ write_base,
386
390
  )
387
391
 
392
+ template = dict(merged)
388
393
  if os.path.isfile(target_env):
389
394
  existing, _ = load_env_file_with_markers(target_env)
390
- merged, _, _ = sync_with_template(merged, tunable_keys, existing)
395
+ sync = sync_with_template(
396
+ template,
397
+ tunable_keys,
398
+ existing,
399
+ base_vars=read_base(target_env),
400
+ derived=derived_keys(product),
401
+ )
402
+ merged = sync.env
403
+ report_sync(sync, os.path.relpath(target_env, product_path))
391
404
  backup_file(target_env, mode=ENV_DEPLOY_MODE)
392
405
  atomic_write(
393
406
  target_env,
@@ -396,6 +409,7 @@ def product_env(generate, merge, env_name, process_all):
396
409
  mode=ENV_DEPLOY_MODE,
397
410
  force_mode=True,
398
411
  )
412
+ write_base(target_env, template)
399
413
  else:
400
414
  atomic_write(
401
415
  target_env,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.28.1
3
+ Version: 1.28.2
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