splent-cli 1.42.0__tar.gz → 1.44.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.42.0/src/splent_cli.egg-info → splent_cli-1.44.0}/PKG-INFO +1 -1
  2. {splent_cli-1.42.0 → splent_cli-1.44.0}/pyproject.toml +1 -1
  3. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/cli.py +12 -3
  4. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_env.py +2 -1
  5. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/clear/clear_uploads.py +2 -2
  6. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_reset.py +1 -1
  7. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_rollback.py +1 -1
  8. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_seed.py +3 -3
  9. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_upgrade.py +1 -1
  10. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/doctor.py +2 -1
  11. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_compile.py +121 -51
  12. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_release.py +159 -27
  13. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_rename.py +2 -3
  14. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_test.py +3 -3
  15. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_commands.py +2 -4
  16. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_create.py +15 -2
  17. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_list.py +1 -2
  18. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_release.py +1 -2
  19. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_test.py +1 -1
  20. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/uvl/uvl_utils.py +4 -11
  21. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/version.py +1 -1
  22. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/compose.py +10 -1
  23. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/context.py +88 -13
  24. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/release.py +9 -1
  25. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/release_dry_run.py +70 -0
  26. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/spl_store.py +3 -2
  27. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +5 -0
  28. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +22 -0
  29. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/ci_services.py +24 -4
  30. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/ci_workflow.py +3 -1
  31. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/dynamic_imports.py +3 -0
  32. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/feature_utils.py +58 -0
  33. {splent_cli-1.42.0 → splent_cli-1.44.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  34. {splent_cli-1.42.0 → splent_cli-1.44.0}/COPYING +0 -0
  35. {splent_cli-1.42.0 → splent_cli-1.44.0}/COPYING.LESSER +0 -0
  36. {splent_cli-1.42.0 → splent_cli-1.44.0}/README.md +0 -0
  37. {splent_cli-1.42.0 → splent_cli-1.44.0}/setup.cfg +0 -0
  38. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/__init__.py +0 -0
  39. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/__main__.py +0 -0
  40. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/__init__.py +0 -0
  41. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  42. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  43. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  44. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  45. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  46. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  47. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  48. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  49. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  50. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/__init__.py +0 -0
  51. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
  52. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  53. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  54. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_features.py +0 -0
  55. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_github.py +0 -0
  56. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  57. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_product.py +0 -0
  58. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  59. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  60. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_releases.py +0 -0
  61. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
  62. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  63. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  64. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  65. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/command/__init__.py +0 -0
  66. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/command/command_create.py +0 -0
  67. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/coverage.py +0 -0
  68. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_console.py +0 -0
  69. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  70. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  71. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  72. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/database/db_status.py +0 -0
  73. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/env/env_list.py +0 -0
  74. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/env/env_set.py +0 -0
  75. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/env/env_show.py +0 -0
  76. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/export/__init__.py +0 -0
  77. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  78. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  79. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  80. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  81. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  82. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  83. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  84. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  85. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  86. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  87. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  88. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  89. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  90. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  91. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  92. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  93. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  94. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  95. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  96. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
  97. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  98. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  99. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  100. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  101. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  102. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  103. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  104. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  105. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  106. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  107. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  108. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  109. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  110. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  111. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  112. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  113. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  114. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  115. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/lint.py +0 -0
  116. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/locust.py +0 -0
  117. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/marketplace/login.py +0 -0
  118. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
  119. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  120. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  121. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  122. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/__init__.py +0 -0
  123. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  124. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_build.py +0 -0
  125. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  126. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_config.py +0 -0
  127. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  128. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_console.py +0 -0
  129. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  130. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  131. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  132. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  133. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_down.py +0 -0
  134. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  135. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_env.py +0 -0
  136. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  137. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  138. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_params.py +0 -0
  139. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_port.py +0 -0
  140. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  141. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  142. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  143. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_run.py +0 -0
  144. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_select.py +0 -0
  145. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  146. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  147. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  148. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_up.py +0 -0
  149. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  150. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/release/__init__.py +0 -0
  151. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/release/release_core.py +0 -0
  152. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/release/release_resume.py +0 -0
  153. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/selenium.py +0 -0
  154. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  155. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  156. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  157. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  158. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  159. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  160. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  161. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  162. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  163. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  164. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  165. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  166. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  167. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  168. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  169. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  170. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  171. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/__init__.py +0 -0
  172. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/credentials.py +0 -0
  173. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/deploy_bundle.py +0 -0
  174. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/image_registry.py +0 -0
  175. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/index_refresh.py +0 -0
  176. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/marketplace.py +0 -0
  177. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/marketplace_api.py +0 -0
  178. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/marketplace_url.py +0 -0
  179. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/neutral_app.py +0 -0
  180. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/own_commands.py +0 -0
  181. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/preflight.py +0 -0
  182. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/pypi_quota.py +0 -0
  183. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/registry.py +0 -0
  184. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/release_gate.py +0 -0
  185. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/services/web_container.py +0 -0
  186. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
  187. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
  188. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
  189. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
  190. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
  191. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
  192. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
  193. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
  194. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
  195. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
  196. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
  197. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
  198. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
  199. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
  200. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
  201. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
  202. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
  203. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
  204. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
  205. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
  206. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
  207. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
  208. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
  209. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
  210. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
  211. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
  212. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
  213. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
  214. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
  215. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
  216. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
  217. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
  218. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
  219. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
  220. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/licenses/COPYING +0 -0
  221. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
  222. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
  223. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
  224. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
  225. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
  226. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
  227. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
  228. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
  229. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
  230. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
  231. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
  232. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
  233. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
  234. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
  235. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_400.html +0 -0
  236. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_401.html +0 -0
  237. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_404.html +0 -0
  238. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_500.html +0 -0
  239. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
  240. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
  241. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
  242. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
  243. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
  244. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_app.css +0 -0
  245. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_app.js +0 -0
  246. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
  247. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
  248. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
  249. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
  250. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
  251. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
  252. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
  253. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
  254. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
  255. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
  256. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
  257. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
  258. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_own.css +0 -0
  259. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
  260. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
  261. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
  262. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/__init__.py +0 -0
  263. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/archetype.py +0 -0
  264. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/cache_utils.py +0 -0
  265. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/command_loader.py +0 -0
  266. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  267. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/db_client.py +0 -0
  268. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/db_utils.py +0 -0
  269. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/decorators.py +0 -0
  270. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/feature_installer.py +0 -0
  271. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/feature_naming.py +0 -0
  272. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/feature_params.py +0 -0
  273. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/feature_requirements.py +0 -0
  274. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/feature_resolution.py +0 -0
  275. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/forge.py +0 -0
  276. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/git_url.py +0 -0
  277. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/integrity.py +0 -0
  278. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/io_utils.py +0 -0
  279. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/lifecycle.py +0 -0
  280. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/manifest.py +0 -0
  281. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/menu_keys.py +0 -0
  282. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/migration_lock.py +0 -0
  283. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/path_utils.py +0 -0
  284. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/proc.py +0 -0
  285. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/redact.py +0 -0
  286. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/refinement_facts.py +0 -0
  287. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/running_version.py +0 -0
  288. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/template_drift.py +0 -0
  289. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/toml_edit.py +0 -0
  290. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
  291. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli/utils/uvl_params.py +0 -0
  292. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli.egg-info/SOURCES.txt +0 -0
  293. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  294. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  295. {splent_cli-1.42.0 → splent_cli-1.44.0}/src/splent_cli.egg-info/requires.txt +0 -0
  296. {splent_cli-1.42.0 → splent_cli-1.44.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.42.0
3
+ Version: 1.44.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.42.0"
7
+ version = "1.44.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"
@@ -5,10 +5,13 @@ import os
5
5
  import click
6
6
  from dotenv import load_dotenv
7
7
 
8
+ from splent_cli.services import context
8
9
  from splent_cli.utils.command_loader import load_commands
9
10
 
10
11
  # From here on os.environ holds the workspace .env as well. What the shell
11
12
  # itself exported is splent_cli.SHELL_ENV, taken before this line ran.
13
+ # Nothing exported is replaced, so a SPLENT_APP given to one command beats
14
+ # the selection in the file (context.active_app has the whole rule).
12
15
  load_dotenv()
13
16
 
14
17
 
@@ -122,7 +125,7 @@ class SPLENTCLI(click.Group):
122
125
  app = get_app()
123
126
  with app.app_context():
124
127
  self._own_cmds_cache = own_commands.collect(
125
- os.getenv("SPLENT_APP"), set(self.commands)
128
+ context.active_app(), set(self.commands)
126
129
  )
127
130
  except Exception as e:
128
131
  if debug:
@@ -170,7 +173,7 @@ class SPLENTCLI(click.Group):
170
173
  """
171
174
  from splent_cli.services import web_container
172
175
 
173
- product = os.getenv("SPLENT_APP")
176
+ product = context.active_app()
174
177
  if not product or web_container.is_product_container():
175
178
  return
176
179
  if not web_container.running(web_container.name(product)):
@@ -196,6 +199,12 @@ class SPLENTCLI(click.Group):
196
199
  from splent_cli.utils.dynamic_imports import get_app
197
200
  from splent_cli.utils.db_utils import check_db_connection
198
201
 
202
+ # For another product than the selection the CLI's container stays
203
+ # on the selection's network (compose.attach_self_to_network), so
204
+ # what reaches this product's services runs where they are.
205
+ if context.is_override():
206
+ self._delegate_to_the_product(ctx)
207
+
199
208
  try:
200
209
  app = get_app()
201
210
  except Exception as exc:
@@ -205,7 +214,7 @@ class SPLENTCLI(click.Group):
205
214
  # Only then: where the app builds here, the command runs here
206
215
  # with the tools the CLI image has (ruff formats migrations).
207
216
  self._delegate_to_the_product(ctx)
208
- product = os.getenv("SPLENT_APP", "The product")
217
+ product = context.active_app() or "The product"
209
218
  raise click.ClickException(
210
219
  f"{product}'s features need {missing}, which the product's "
211
220
  "image installs and the CLI's does not.\n"
@@ -8,6 +8,7 @@ import sys
8
8
  import click
9
9
  import importlib.metadata
10
10
 
11
+ from splent_cli.services import context
11
12
  from splent_cli.services.release_gate import pypi_credentials
12
13
  from splent_cli.utils import forge
13
14
 
@@ -61,7 +62,7 @@ def check_env():
61
62
  _warn(".env file not found in workspace")
62
63
 
63
64
  # SPLENT_APP
64
- app = os.getenv("SPLENT_APP")
65
+ app = context.active_app(workspace)
65
66
  if app:
66
67
  app_path = os.path.join(workspace, app)
67
68
  if os.path.isdir(app_path):
@@ -99,7 +99,7 @@ def resolve_uploads_dir(app=None) -> str:
99
99
  product or its package, anything holding one of them, or a directory with
100
100
  code at its top.
101
101
  """
102
- product = os.getenv("SPLENT_APP", "")
102
+ product = context.active_app() or ""
103
103
  if not product or product in (".", "..") or os.sep in product or "/" in product:
104
104
  raise UploadsDirError(f"'{product}' is not a valid product name.")
105
105
 
@@ -279,7 +279,7 @@ def clear_uploads(env_dev, env_prod, yes):
279
279
  click.echo(click.style("Cannot specify both --dev and --prod.", fg="red"))
280
280
  raise SystemExit(1)
281
281
  env = context.resolve_env(env_dev, env_prod)
282
- _run_where_the_uploads_are(os.getenv("SPLENT_APP", ""), env, yes)
282
+ _run_where_the_uploads_are(context.active_app() or "", env, yes)
283
283
 
284
284
  try:
285
285
  uploads_dir = resolve_uploads_dir()
@@ -199,7 +199,7 @@ def db_reset(feature_name, yes):
199
199
  """
200
200
  app = current_app
201
201
  product_path = PathUtils.get_app_base_dir()
202
- product_name = os.getenv("SPLENT_APP", "")
202
+ product_name = context.active_app() or ""
203
203
 
204
204
  if feature_name:
205
205
  _reset_one_feature(app, feature_name, yes, product_path, product_name)
@@ -140,7 +140,7 @@ def _downgrade(directory: str, revision: str) -> None:
140
140
  def db_rollback(feature, steps, to_base, cascade):
141
141
  app = current_app
142
142
  product_path = PathUtils.get_app_base_dir()
143
- product_name = os.getenv("SPLENT_APP", "")
143
+ product_name = context.active_app() or ""
144
144
 
145
145
  if to_base:
146
146
  source = click.get_current_context().get_parameter_source("steps")
@@ -23,7 +23,7 @@ def _resolve_feature_order(features_raw: list[str]) -> list[str]:
23
23
  feature it refines (the refinement metadata the boot reads), with or
24
24
  without a UVL. Features with neither keep their declared order.
25
25
  """
26
- splent_app = os.getenv("SPLENT_APP", "")
26
+ splent_app = context.active_app() or ""
27
27
  working_dir = os.getenv("WORKING_DIR", "/workspace")
28
28
  product_dir = os.path.join(working_dir, splent_app) if splent_app else ""
29
29
 
@@ -112,7 +112,7 @@ def _product_seeders(errors):
112
112
  They are always loaded, also when a single module is seeded, because
113
113
  their ``replaces`` decide which feature seeders may run at all.
114
114
  """
115
- product = os.getenv("SPLENT_APP", "")
115
+ product = context.active_app()
116
116
  if not product:
117
117
  return []
118
118
  return _load_seeders(f"{product}.seeders", errors, label="product seeders")
@@ -154,7 +154,7 @@ def get_installed_seeders(specific_module=None):
154
154
  )
155
155
 
156
156
  targets = _module_names(specific_module) if specific_module else None
157
- if targets and os.getenv("SPLENT_APP", "") in targets:
157
+ if targets and context.active_app() in targets:
158
158
  return product_seeders, errors
159
159
 
160
160
  seeders = []
@@ -79,7 +79,7 @@ def _mark_without_migrations(
79
79
 
80
80
  def _resolve_product():
81
81
  """Return (product_path, product_name) from env."""
82
- product = os.getenv("SPLENT_APP", "")
82
+ product = context.active_app() or ""
83
83
  product_path = PathUtils.get_app_base_dir()
84
84
  return product_path, product
85
85
 
@@ -19,6 +19,7 @@ from splent_cli.commands.check.check_product import check_product
19
19
  from splent_cli.commands.feature.feature_status import feature_status
20
20
  from splent_cli.commands.database.db_status import db_status
21
21
  from splent_cli.commands.product.product_containers import product_docker
22
+ from splent_cli.services import context
22
23
 
23
24
 
24
25
  SECTIONS = [
@@ -74,7 +75,7 @@ def _check_workspace_precondition():
74
75
  elif not os.path.isdir(wd):
75
76
  problems.append(f"WORKING_DIR points to a missing directory: {wd}")
76
77
 
77
- app = os.getenv("SPLENT_APP")
78
+ app = context.active_app(workspace)
78
79
  if not app:
79
80
  problems.append("SPLENT_APP is not set")
80
81
  elif not os.path.isdir(os.path.join(workspace, app)):
@@ -123,6 +123,63 @@ def _container_workspace(container_id: str, workspace: str) -> tuple[str | None,
123
123
  return None, mounted
124
124
 
125
125
 
126
+ def webpack_container(product: str, env: str, workspace: str) -> tuple[str | None, str]:
127
+ """Where webpack runs for *product*: ``(container id, its workspace path)``.
128
+
129
+ The container is None inside the product's web container, where webpack
130
+ is at hand. Elsewhere (the CLI container, the host) it is the product's
131
+ running web container of *env*, and the path is where that container
132
+ sees *workspace*. Raises ClickException when there is none to run in.
133
+ feature:release --dry-run asks it too, to say whether the bundle can be
134
+ built.
135
+ """
136
+ if _is_product_container():
137
+ return None, workspace
138
+
139
+ product_path = os.path.join(workspace, product)
140
+ docker_dir = os.path.join(product_path, "docker")
141
+ compose_file = compose.resolve_file(product_path, env)
142
+
143
+ if not compose_file:
144
+ raise click.ClickException(
145
+ f"No docker-compose file found for {product} ({env})"
146
+ )
147
+
148
+ project_name = compose.project_name(product, env)
149
+ container_id = compose.find_main_container(project_name, compose_file, docker_dir)
150
+ host = _on_host()
151
+ if not container_id and host:
152
+ container_id = _web_container_by_label(product, env)
153
+
154
+ if not container_id:
155
+ raise click.ClickException(
156
+ f"No running container found for {product} ({env}). "
157
+ f"Start it with splent product:up --{env}"
158
+ )
159
+
160
+ if not host:
161
+ return container_id, workspace
162
+
163
+ # webpack runs in the container, so it needs the container's paths, and
164
+ # only a container that mounts this workspace has the sources being
165
+ # compiled.
166
+ container_workspace, mounted = _container_workspace(container_id, workspace)
167
+ if container_workspace is None and mounted:
168
+ raise click.ClickException(
169
+ f"The {product} web container runs the workspace at "
170
+ f"{mounted}, not this one ({workspace}). Start this "
171
+ "workspace's product, or compile from the workspace that "
172
+ "runs it."
173
+ )
174
+ if container_workspace is None:
175
+ raise click.ClickException(
176
+ f"The {product} web container does not mount this "
177
+ f"workspace ({workspace}), so webpack cannot reach the "
178
+ "sources there."
179
+ )
180
+ return container_id, container_workspace
181
+
182
+
126
183
  @click.command(
127
184
  "feature:compile",
128
185
  short_help="Compile frontend assets (webpack) for one or all features.",
@@ -131,66 +188,32 @@ def _container_workspace(container_id: str, workspace: str) -> tuple[str | None,
131
188
  @click.option("--watch", is_flag=True, help="Enable watch mode for development.")
132
189
  @click.option("--dev", "env_dev", is_flag=True, help="Use development environment.")
133
190
  @click.option("--prod", "env_prod", is_flag=True, help="Use production environment.")
134
- def feature_compile(feature_name, watch, env_dev, env_prod):
135
- production = os.getenv("FLASK_ENV", "develop") == "production" or env_prod
191
+ @click.option(
192
+ "--release",
193
+ "release_build",
194
+ is_flag=True,
195
+ help="Build the bundle a release ships: production mode, into an emptied "
196
+ "assets/dist, and fail when the feature has nothing to build. "
197
+ "feature:release runs it.",
198
+ )
199
+ def feature_compile(feature_name, watch, env_dev, env_prod, release_build):
200
+ if release_build and watch:
201
+ raise click.UsageError("--release builds the bundle once; drop --watch.")
202
+ production = (
203
+ os.getenv("FLASK_ENV", "develop") == "production" or env_prod or release_build
204
+ )
136
205
 
137
206
  features = (
138
- [get_normalize_feature_name_in_splent_format(feature_name)]
139
- if feature_name
140
- else get_features_from_pyproject()
207
+ [_feature_ref(feature_name)] if feature_name else get_features_from_pyproject()
141
208
  )
142
209
 
143
210
  env = context.resolve_env(env_dev, env_prod)
144
211
  product = context.require_app()
145
212
  workspace = str(context.workspace())
146
- product_path = os.path.join(workspace, product)
147
213
 
148
214
  # If running inside the product's web container, webpack is available locally.
149
215
  # Otherwise (CLI container or host), delegate to the product container via docker exec.
150
- container_workspace = workspace
151
- if _is_product_container():
152
- container_id = None
153
- else:
154
- docker_dir = os.path.join(product_path, "docker")
155
- compose_file = compose.resolve_file(product_path, env)
156
-
157
- if not compose_file:
158
- raise click.ClickException(
159
- f"No docker-compose file found for {product} ({env})"
160
- )
161
-
162
- project_name = compose.project_name(product, env)
163
- container_id = compose.find_main_container(
164
- project_name, compose_file, docker_dir
165
- )
166
- host = _on_host()
167
- if not container_id and host:
168
- container_id = _web_container_by_label(product, env)
169
-
170
- if not container_id:
171
- raise click.ClickException(
172
- f"No running container found for {product} ({env}). "
173
- f"Start it with splent product:up --{env}"
174
- )
175
-
176
- if host:
177
- # webpack runs in the container, so it needs the container's
178
- # paths, and only a container that mounts this workspace has the
179
- # sources being compiled.
180
- container_workspace, mounted = _container_workspace(container_id, workspace)
181
- if container_workspace is None and mounted:
182
- raise click.ClickException(
183
- f"The {product} web container runs the workspace at "
184
- f"{mounted}, not this one ({workspace}). Start this "
185
- "workspace's product, or compile from the workspace that "
186
- "runs it."
187
- )
188
- if container_workspace is None:
189
- raise click.ClickException(
190
- f"The {product} web container does not mount this "
191
- f"workspace ({workspace}), so webpack cannot reach the "
192
- "sources there."
193
- )
216
+ container_id, container_workspace = webpack_container(product, env, workspace)
194
217
 
195
218
  # Every feature is attempted, so one run reports every broken bundle, and
196
219
  # the exit code then says whether any failed: the production Dockerfile
@@ -206,6 +229,7 @@ def feature_compile(feature_name, watch, env_dev, env_prod):
206
229
  workspace,
207
230
  product,
208
231
  container_workspace=container_workspace,
232
+ release_build=release_build,
209
233
  )
210
234
  ]
211
235
  if failed and not watch:
@@ -217,6 +241,16 @@ def feature_compile(feature_name, watch, env_dev, env_prod):
217
241
  raise SystemExit(1)
218
242
 
219
243
 
244
+ def _feature_ref(name: str) -> str:
245
+ """*name* as typed, with the ``splent_feature_`` prefix on the feature's
246
+ own name: ``auth`` and ``splent_io/auth`` are ``splent_feature_auth``
247
+ and ``splent_io/splent_feature_auth``. feature:release passes the
248
+ namespaced ref, and prefixing the whole of it looked for
249
+ ``splent_feature_splent_io/...`` and found no webpack config."""
250
+ namespace, sep, base = name.rpartition("/")
251
+ return f"{namespace}{sep}{get_normalize_feature_name_in_splent_format(base)}"
252
+
253
+
220
254
  def _find_webpack(workspace, product, org_safe, base_name, version):
221
255
  """Locate webpack.config.js for an editable feature.
222
256
 
@@ -242,6 +276,7 @@ def _compile_in_container(
242
276
  workspace,
243
277
  product,
244
278
  container_workspace=None,
279
+ release_build=False,
245
280
  ):
246
281
  """Compile one feature's assets. False only when webpack itself failed;
247
282
  a skipped feature (pinned, or without webpack.config.js) is not a failure.
@@ -249,6 +284,12 @@ def _compile_in_container(
249
284
  *container_workspace* is where the container that runs webpack sees the
250
285
  workspace. It differs from *workspace* only when the command runs on the
251
286
  host and delegates to the product's container.
287
+
288
+ *release_build* is feature:release's build (``--release``): webpack
289
+ empties assets/dist before it writes the bundle, so nothing a former
290
+ build left there ships with it, and a feature that would be skipped is a
291
+ failure, because the release would otherwise go on with whatever bundle
292
+ was there.
252
293
  """
253
294
  parts = feature.split("/")
254
295
  if len(parts) == 2:
@@ -263,6 +304,13 @@ def _compile_in_container(
263
304
  # Pinned features are immutable — never compile them.
264
305
  # Assets must be included at release time via feature:release.
265
306
  if version:
307
+ if release_build:
308
+ click.secho(
309
+ f"❌ {feature} is pinned ({version}): a release builds the "
310
+ "bundle of the working copy at the workspace root.",
311
+ fg="red",
312
+ )
313
+ return False
266
314
  click.echo(
267
315
  click.style(
268
316
  f" {feature} is pinned ({version}), skipping.",
@@ -274,6 +322,13 @@ def _compile_in_container(
274
322
  webpack_file = _find_webpack(workspace, product, org_safe, base_name, version)
275
323
 
276
324
  if not webpack_file:
325
+ if release_build:
326
+ click.secho(
327
+ f"❌ No webpack.config.js found in {feature} at the workspace "
328
+ "root, so there is no bundle to build.",
329
+ fg="red",
330
+ )
331
+ return False
277
332
  click.echo(
278
333
  click.style(
279
334
  f"⚠ No webpack.config.js found in {feature}, skipping...", fg="yellow"
@@ -300,6 +355,8 @@ def _compile_in_container(
300
355
  cmd_parts.append("--watch")
301
356
  if not production:
302
357
  cmd_parts.extend(["--devtool=source-map", "--no-cache"])
358
+ if release_build:
359
+ cmd_parts.append("--output-clean")
303
360
 
304
361
  shell_cmd = " ".join(shlex.quote(p) for p in cmd_parts)
305
362
  cd_cmd = f"cd {shlex.quote(product_root)} && {shell_cmd}"
@@ -308,7 +365,20 @@ def _compile_in_container(
308
365
  # From the host: run via `docker exec` so webpack uses the container's
309
366
  # node_modules.
310
367
  if container_id:
311
- run_cmd = ["docker", "exec", container_id, "bash", "-c", cd_cmd]
368
+ # As the workspace's owner, as feature:test runs there: an exec runs
369
+ # as root in a development image, and a file webpack creates in
370
+ # assets/dist (a release's <bundle>.LICENSE.txt) stayed root's.
371
+ from splent_cli.commands.feature.feature_test import workspace_user
372
+
373
+ run_cmd = [
374
+ "docker",
375
+ "exec",
376
+ *workspace_user(container_id),
377
+ container_id,
378
+ "bash",
379
+ "-c",
380
+ cd_cmd,
381
+ ]
312
382
  tool_hint = (
313
383
  "Install Docker Desktop or Docker Engine: "
314
384
  "https://docs.docker.com/get-docker/"
@@ -1845,38 +1845,164 @@ def _commands_help_block(commands_help: dict) -> str:
1845
1845
  return "".join(lines)
1846
1846
 
1847
1847
 
1848
- # ── Compile assets ────────────────────────────────────────────────────
1848
+ # ── Frontend bundle ───────────────────────────────────────────────────
1849
1849
 
1850
1850
 
1851
- def _compile_before_release(feature_name: str):
1852
- """Build the feature's frontend bundles, which the package ships.
1851
+ # How long the production build of one feature may take. A build cut short
1852
+ # stops the release, so the bound is generous: TinyMCE's takes seconds.
1853
+ _BUNDLE_TIMEOUT = 600
1853
1854
 
1854
- MANIFEST.in puts assets/dist in the wheel, so this runs before the build.
1855
- It runs this same CLI (``sys.executable -m splent_cli``), not whatever
1856
- ``splent`` is first on PATH, and webpack runs in the product's web
1857
- container, found from the host too. A failure never stops the release:
1858
- the package then ships the bundle the working tree already had.
1855
+
1856
+ def _assets_dir(feature_path: str, namespace: str, feature_name: str) -> Path:
1857
+ """``src/<org>/<feature>/assets``: the webpack config is in js/, and
1858
+ webpack writes the bundle to dist/."""
1859
+ return (
1860
+ Path(feature_path)
1861
+ / "src"
1862
+ / normalize_namespace(namespace)
1863
+ / feature_name
1864
+ / "assets"
1865
+ )
1866
+
1867
+
1868
+ def _bundle_files(feature_path: str, dist: Path) -> dict[str, str]:
1869
+ """The sha256 of every file under *dist*, by path relative to the feature."""
1870
+ import hashlib
1871
+
1872
+ if not dist.is_dir():
1873
+ return {}
1874
+ return {
1875
+ path.relative_to(feature_path).as_posix(): hashlib.sha256(
1876
+ path.read_bytes()
1877
+ ).hexdigest()
1878
+ for path in sorted(dist.rglob("*"))
1879
+ if path.is_file()
1880
+ }
1881
+
1882
+
1883
+ def _refuse_bundle(feature_ref: str, reason: str, output: str = "") -> None:
1884
+ click.secho(
1885
+ f" error: the frontend bundle of {feature_ref} was not built: {reason}.",
1886
+ fg="red",
1887
+ )
1888
+ lines = [line for line in output.splitlines() if line.strip()][-8:]
1889
+ for line in lines:
1890
+ click.secho(f" {line}", fg="bright_black")
1891
+ click.echo(
1892
+ " The release tags the bundle with the code, so it does not go on "
1893
+ "without one.\n"
1894
+ " webpack runs in the product's web container (splent product:up "
1895
+ "--dev starts it).\n"
1896
+ f" Try the build on its own with: splent feature:compile {feature_ref} "
1897
+ "--release\n"
1898
+ " No version bump, no commit, no tag, no release."
1899
+ )
1900
+ raise SystemExit(1)
1901
+
1902
+
1903
+ def _build_bundle(feature_path: str, namespace: str, feature_name: str):
1904
+ """Build the frontend bundle the release ships, or stop the release.
1905
+
1906
+ None for a feature without ``assets/js/webpack.config.js``, which has no
1907
+ bundle. Otherwise the sha256 of every file the build left in
1908
+ ``assets/dist``, by path relative to the feature, for
1909
+ :func:`_stage_bundle`.
1910
+
1911
+ Every channel installs what the tag holds: the snapshot in .splent_cache
1912
+ is a clone of it, pip builds a git+ install from it, and the wheel is
1913
+ built from the same tree. assets/dist is gitignored, so the tags of 13
1914
+ of uvlhub's 14 webpack features carried no bundle. Development served it
1915
+ from the working copy at the workspace root, and the forge CI and an
1916
+ image that installed from a tag answered 404. The release builds the
1917
+ bundle in production mode, into an emptied assets/dist, before the
1918
+ tests, and its commit carries it, so the tag, the wheel and the sdist
1919
+ hold the same bytes (SD-01: what is verified is what is deployed).
1920
+
1921
+ A build that cannot run, fails, or writes no bundle stops the release
1922
+ before anything versioned changes. It used to go on, and the package
1923
+ then shipped whatever bundle the working tree had, stale or none.
1859
1924
  """
1860
- click.echo(" compile building frontend assets...")
1925
+ assets = _assets_dir(feature_path, namespace, feature_name)
1926
+ if not (assets / "js" / "webpack.config.js").is_file():
1927
+ return None
1928
+ feature_ref = f"{namespace}/{feature_name}"
1929
+ click.echo(" bundle building assets/dist in production mode...")
1930
+ # This same CLI (sys.executable -m splent_cli), not whatever splent is
1931
+ # first on PATH, and webpack runs in the product's web container, found
1932
+ # from the host too.
1861
1933
  try:
1862
1934
  result = subprocess.run(
1863
- [sys.executable, "-m", "splent_cli", "feature:compile", feature_name],
1935
+ [
1936
+ sys.executable,
1937
+ "-m",
1938
+ "splent_cli",
1939
+ "feature:compile",
1940
+ feature_ref,
1941
+ "--release",
1942
+ ],
1864
1943
  capture_output=True,
1865
1944
  text=True,
1866
- timeout=120,
1945
+ timeout=_BUNDLE_TIMEOUT,
1867
1946
  )
1868
1947
  except subprocess.TimeoutExpired:
1869
- click.secho(" compile timed out (120s)", fg="yellow")
1870
- return
1948
+ _refuse_bundle(
1949
+ feature_ref, f"webpack did not finish within {_BUNDLE_TIMEOUT} seconds"
1950
+ )
1871
1951
  except OSError as e:
1872
- click.secho(f" compile skipped, the CLI could not run ({e})", fg="yellow")
1873
- return
1874
- if result.returncode == 0:
1875
- click.echo(" compile done")
1876
- return
1877
- output = (result.stderr or result.stdout or "").strip()
1878
- detail = output.removeprefix("Error: ").strip()[:200] or "feature:compile failed"
1879
- click.secho(f" compile skipped, {detail}", fg="yellow")
1952
+ _refuse_bundle(feature_ref, f"the CLI could not run ({e})")
1953
+ if result.returncode != 0:
1954
+ output = "\n".join(part for part in (result.stdout, result.stderr) if part)
1955
+ _refuse_bundle(feature_ref, "feature:compile --release failed", output)
1956
+
1957
+ built = _bundle_files(feature_path, assets / "dist")
1958
+ if not any(path.endswith(".js") for path in built):
1959
+ _refuse_bundle(feature_ref, "webpack wrote no bundle to assets/dist")
1960
+ click.echo(f" bundle built, {len(built)} file(s) in assets/dist")
1961
+ return built
1962
+
1963
+
1964
+ def _stage_bundle(feature_path: str, namespace: str, feature_name: str, built) -> None:
1965
+ """Put the bundle :func:`_build_bundle` built into the release commit.
1966
+
1967
+ assets/dist is gitignored, and the release commit stages what git tracks
1968
+ and the new files it does not ignore, so the bundle is added here by
1969
+ name (``git add -f``). A file the build no longer writes, a bundle
1970
+ renamed or a source map dropped, is a tracked file gone, which the
1971
+ commit stages as a deletion.
1972
+
1973
+ The commit has to carry the bundle the build wrote. Anything that
1974
+ rewrote assets/dist since (a watch build of the same working copy)
1975
+ would tag another one, and that stops the release here.
1976
+ """
1977
+ dist = _assets_dir(feature_path, namespace, feature_name) / "dist"
1978
+ if _bundle_files(feature_path, dist) != built:
1979
+ click.secho(
1980
+ " error: assets/dist changed after the release built it, so the "
1981
+ "release commit would carry another bundle.",
1982
+ fg="red",
1983
+ )
1984
+ click.echo(
1985
+ " Stop whatever rebuilds it (feature:compile --watch) and release "
1986
+ "again.\n"
1987
+ " The version was bumped but not committed."
1988
+ )
1989
+ raise SystemExit(1)
1990
+ try:
1991
+ subprocess.run(
1992
+ ["git", "add", "-f", "--", *built],
1993
+ cwd=feature_path,
1994
+ check=True,
1995
+ capture_output=True,
1996
+ text=True,
1997
+ )
1998
+ except subprocess.CalledProcessError as e:
1999
+ click.secho(
2000
+ f" error: git could not stage the bundle: {(e.stderr or '').strip() or e}",
2001
+ fg="red",
2002
+ )
2003
+ click.echo(" The version was bumped but not committed.")
2004
+ raise SystemExit(1)
2005
+ click.echo(" bundle assets/dist goes into the release commit")
1880
2006
 
1881
2007
 
1882
2008
  # ── Versioned snapshot ────────────────────────────────────────────────
@@ -2183,17 +2309,22 @@ def feature_release(
2183
2309
  else "this package does not publish to the forge"
2184
2310
  )
2185
2311
 
2312
+ # The bundle the release ships, by path: built before the tests, so they
2313
+ # run against it, and staged into the release commit, so the tag, the
2314
+ # wheel and the sdist all carry it.
2315
+ bundle: dict = {}
2316
+
2317
+ def _build(path, ver):
2318
+ bundle["files"] = _build_bundle(path, namespace, feature_name)
2319
+
2186
2320
  def _pre_commit(path, ver):
2187
2321
  click.echo(" contract updating from source code...")
2188
2322
  from splent_cli.commands.feature.feature_contract import update_contract
2189
2323
 
2190
2324
  update_contract(path, namespace, feature_name)
2191
2325
  click.echo(" contract written to pyproject.toml")
2192
- # Before the package is built, because the wheel ships assets/dist.
2193
- # Before the local release commit too, which stages everything, so a
2194
- # feature that tracks its bundles in git (auth does) commits the
2195
- # bundle just built, the same one the wheel ships.
2196
- _compile_before_release(feature_name)
2326
+ if bundle.get("files") is not None:
2327
+ _stage_bundle(path, namespace, feature_name, bundle["files"])
2197
2328
 
2198
2329
  def _post_pypi(path, ver):
2199
2330
  if release_gate.FORGE in channels:
@@ -2221,6 +2352,7 @@ def feature_release(
2221
2352
  channels=channels,
2222
2353
  resume_target=feature_name,
2223
2354
  allow_unfinished=allow_unfinished,
2355
+ build_hook=_build,
2224
2356
  pre_commit_hook=_pre_commit,
2225
2357
  post_pypi_hook=_post_pypi,
2226
2358
  wait=wait,
@@ -2440,7 +2572,7 @@ def _dry_run(
2440
2572
 
2441
2573
  tag = f"v{version.lstrip('v')}"
2442
2574
  git_state = release_dry_run.read_git_state(feature_path, tag)
2443
- product = os.getenv("SPLENT_APP") or None
2575
+ product = context.active_app()
2444
2576
 
2445
2577
  rehearsal = release_dry_run.rehearse_feature_release(
2446
2578
  feature_path=feature_path,