splent-cli 1.54.0__tar.gz → 1.55.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 (326) hide show
  1. {splent_cli-1.54.0/src/splent_cli.egg-info → splent_cli-1.55.0}/PKG-INFO +1 -1
  2. {splent_cli-1.54.0 → splent_cli-1.55.0}/pyproject.toml +1 -1
  3. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_pip_install.py +198 -3
  4. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +108 -45
  5. {splent_cli-1.54.0 → splent_cli-1.55.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  6. {splent_cli-1.54.0 → splent_cli-1.55.0}/COPYING +0 -0
  7. {splent_cli-1.54.0 → splent_cli-1.55.0}/COPYING.LESSER +0 -0
  8. {splent_cli-1.54.0 → splent_cli-1.55.0}/README.md +0 -0
  9. {splent_cli-1.54.0 → splent_cli-1.55.0}/setup.cfg +0 -0
  10. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/__init__.py +0 -0
  11. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/__main__.py +0 -0
  12. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/cli.py +0 -0
  13. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/__init__.py +0 -0
  14. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  15. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  16. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  17. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  18. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  19. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  20. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  21. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  22. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  23. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/__init__.py +0 -0
  24. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
  25. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  26. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  27. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_env.py +0 -0
  28. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_features.py +0 -0
  29. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_github.py +0 -0
  30. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  31. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_product.py +0 -0
  32. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  33. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  34. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_releases.py +0 -0
  35. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
  36. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  37. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  38. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  39. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  40. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/command/__init__.py +0 -0
  41. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/command/command_create.py +0 -0
  42. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/coverage.py +0 -0
  43. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_console.py +0 -0
  44. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  45. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  46. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_reset.py +0 -0
  47. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  48. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  49. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  50. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_status.py +0 -0
  51. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  52. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/doctor.py +0 -0
  53. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/env/env_list.py +0 -0
  54. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/env/env_set.py +0 -0
  55. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/env/env_show.py +0 -0
  56. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/export/__init__.py +0 -0
  57. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  58. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  59. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  60. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  61. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  62. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  63. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  64. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  65. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  66. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  67. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  68. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  69. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  70. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  71. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  72. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_frontend_init.py +0 -0
  73. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  74. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  75. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  76. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  77. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  78. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
  79. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  80. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  81. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  82. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_npm.py +0 -0
  83. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  84. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  85. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  86. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  87. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  88. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
  89. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  90. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  91. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  92. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_security.py +0 -0
  93. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  94. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  95. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
  96. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  97. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  98. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  99. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  100. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  101. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/lint.py +0 -0
  102. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/locust.py +0 -0
  103. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/marketplace/login.py +0 -0
  104. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
  105. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  106. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  107. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  108. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/__init__.py +0 -0
  109. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  110. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_build.py +0 -0
  111. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  112. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  113. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_config.py +0 -0
  114. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  115. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_console.py +0 -0
  116. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  117. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_create.py +0 -0
  118. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  119. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  120. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  121. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_down.py +0 -0
  122. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  123. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_env.py +0 -0
  124. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_frontend_audit.py +0 -0
  125. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_list.py +0 -0
  126. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  127. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  128. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_params.py +0 -0
  129. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_port.py +0 -0
  130. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_release.py +0 -0
  131. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  132. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  133. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  134. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_run.py +0 -0
  135. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_select.py +0 -0
  136. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  137. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  138. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  139. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_test.py +0 -0
  140. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_ui_audit.py +0 -0
  141. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_up.py +0 -0
  142. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  143. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/release/__init__.py +0 -0
  144. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/release/release_core.py +0 -0
  145. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/release/release_resume.py +0 -0
  146. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/selenium.py +0 -0
  147. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  148. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  149. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  150. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  151. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  152. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  153. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  154. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  155. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  156. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  157. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  158. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  159. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  160. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  161. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  162. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  163. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/storage/__init__.py +0 -0
  164. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/storage/storage_migrate.py +0 -0
  165. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  166. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  167. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/commands/version.py +0 -0
  168. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/__init__.py +0 -0
  169. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/compose.py +0 -0
  170. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/context.py +0 -0
  171. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/credentials.py +0 -0
  172. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/deploy_bundle.py +0 -0
  173. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/editable_refresh.py +0 -0
  174. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/frontend.py +0 -0
  175. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/image_registry.py +0 -0
  176. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/index_refresh.py +0 -0
  177. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/marketplace.py +0 -0
  178. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/marketplace_api.py +0 -0
  179. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/marketplace_url.py +0 -0
  180. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/neutral_app.py +0 -0
  181. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/own_commands.py +0 -0
  182. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/preflight.py +0 -0
  183. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/pypi_quota.py +0 -0
  184. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/registry.py +0 -0
  185. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/release.py +0 -0
  186. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/release_dry_run.py +0 -0
  187. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/release_gate.py +0 -0
  188. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/release_workspace.py +0 -0
  189. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/security_scan.py +0 -0
  190. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/source_mirror.py +0 -0
  191. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/spl_store.py +0 -0
  192. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/ui_audit.py +0 -0
  193. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/services/web_container.py +0 -0
  194. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
  195. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
  196. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
  197. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
  198. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
  199. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
  200. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
  201. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
  202. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
  203. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_frontend.npmrc.j2 +0 -0
  204. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_frontend_scripts.js.j2 +0 -0
  205. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
  206. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
  207. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
  208. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
  209. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
  210. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
  211. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
  212. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
  213. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
  214. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
  215. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
  216. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
  217. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
  218. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
  219. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
  220. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
  221. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
  222. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
  223. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
  224. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
  225. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
  226. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
  227. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
  228. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
  229. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
  230. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
  231. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/licenses/COPYING +0 -0
  232. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
  233. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
  234. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
  235. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
  236. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
  237. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
  238. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
  239. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
  240. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
  241. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
  242. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
  243. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
  244. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
  245. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
  246. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_400.html +0 -0
  247. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_401.html +0 -0
  248. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_404.html +0 -0
  249. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_500.html +0 -0
  250. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
  251. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
  252. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
  253. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
  254. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_app.css +0 -0
  255. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_app.js +0 -0
  256. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
  257. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
  258. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
  259. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
  260. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
  261. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
  262. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
  263. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
  264. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
  265. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
  266. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
  267. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
  268. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
  269. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_own.css +0 -0
  270. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
  271. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
  272. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
  273. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/templates/ui_audit/measure.js +0 -0
  274. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/Dockerfile.frontend +0 -0
  275. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/package-lock.json +0 -0
  276. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/package.json +0 -0
  277. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/splent/build.js +0 -0
  278. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/splent/legacy.js +0 -0
  279. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/splent/lint.js +0 -0
  280. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/splent/plugin.js +0 -0
  281. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/splent/preset.js +0 -0
  282. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/toolchain/frontend/splent/singletons.json +0 -0
  283. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/__init__.py +0 -0
  284. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/archetype.py +0 -0
  285. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/cache_utils.py +0 -0
  286. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/ci_services.py +0 -0
  287. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/ci_workflow.py +0 -0
  288. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/command_loader.py +0 -0
  289. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  290. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/db_client.py +0 -0
  291. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/db_utils.py +0 -0
  292. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/decorators.py +0 -0
  293. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/deploy_freshness.py +0 -0
  294. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  295. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/feature_installer.py +0 -0
  296. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/feature_naming.py +0 -0
  297. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/feature_params.py +0 -0
  298. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/feature_requirements.py +0 -0
  299. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/feature_resolution.py +0 -0
  300. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/feature_utils.py +0 -0
  301. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/forge.py +0 -0
  302. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/frontend_check.py +0 -0
  303. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/frontend_licenses.py +0 -0
  304. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/git_url.py +0 -0
  305. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/integrity.py +0 -0
  306. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/io_utils.py +0 -0
  307. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/lifecycle.py +0 -0
  308. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/manifest.py +0 -0
  309. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/menu_keys.py +0 -0
  310. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/migration_lock.py +0 -0
  311. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/npm_semver.py +0 -0
  312. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/path_utils.py +0 -0
  313. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/proc.py +0 -0
  314. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/redact.py +0 -0
  315. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/refinement_facts.py +0 -0
  316. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/running_version.py +0 -0
  317. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/template_drift.py +0 -0
  318. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/toml_edit.py +0 -0
  319. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
  320. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/uvl_diagnosis.py +0 -0
  321. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli/utils/uvl_params.py +0 -0
  322. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli.egg-info/SOURCES.txt +0 -0
  323. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  324. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  325. {splent_cli-1.54.0 → splent_cli-1.55.0}/src/splent_cli.egg-info/requires.txt +0 -0
  326. {splent_cli-1.54.0 → splent_cli-1.55.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.54.0
3
+ Version: 1.55.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.54.0"
7
+ version = "1.55.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"
@@ -15,6 +15,13 @@ and because a private feature may deliberately never be published there.
15
15
  Without the fallback, one unpublished feature makes the whole product
16
16
  unbuildable even though its code is tagged and reachable.
17
17
 
18
+ Everything is installed by one pip run. Which channel serves each feature is
19
+ settled first, asking PyPI and the forge for all of them at once, and a tag is
20
+ named by the commit it points at: pip keeps the wheel it builds from a commit
21
+ in its cache, and the next build takes it from there instead of cloning and
22
+ building the feature again. Only when that run fails are the features
23
+ installed one at a time, which says which one failed and why.
24
+
18
25
  Three things about this are worth stating plainly rather than discovering:
19
26
 
20
27
  * only [tool.splent].features is read. features_dev and features_prod are
@@ -39,7 +46,9 @@ Example:
39
46
  """
40
47
 
41
48
  import os
49
+ import subprocess
42
50
  import sys
51
+ from concurrent.futures import ThreadPoolExecutor
43
52
 
44
53
  import click
45
54
  import tomllib
@@ -154,6 +163,180 @@ def _last_line(output: str) -> str:
154
163
  return _redacted(lines[-1]) if lines else "no output"
155
164
 
156
165
 
166
+ # How many PyPI and forge questions are asked at once.
167
+ PROBE_WORKERS = 16
168
+
169
+ # Seconds one question to PyPI or one ``git ls-remote`` may take.
170
+ PROBE_TIMEOUT = 20
171
+
172
+ # What a failed pip run is summed up with, counted from its end.
173
+ TAIL_LINES = 15
174
+
175
+
176
+ def _asks_pypi() -> bool:
177
+ """Whether pip installs from PyPI, so PyPI is the one to ask.
178
+
179
+ A PIP_INDEX_URL that points elsewhere (a mirror, a private index) serves
180
+ what PyPI does not say, so nothing is asked and the features are
181
+ installed one at a time, each trying its index first.
182
+ """
183
+ index = os.getenv("PIP_INDEX_URL", "").strip()
184
+ return not index or index.rstrip("/") in (
185
+ "https://pypi.org/simple",
186
+ "https://pypi.python.org/simple",
187
+ )
188
+
189
+
190
+ def _pypi_serves(dist: str, version: str) -> bool | None:
191
+ """Whether PyPI serves ``dist==version``, None when it could not be asked."""
192
+ import requests
193
+
194
+ url = f"https://pypi.org/pypi/{dist}/{version}/json"
195
+ for _attempt in range(2):
196
+ try:
197
+ response = requests.get(url, timeout=PROBE_TIMEOUT)
198
+ except requests.RequestException:
199
+ continue
200
+ if response.status_code == 200:
201
+ return True
202
+ if response.status_code == 404:
203
+ return False
204
+ return None
205
+
206
+
207
+ def _tag_commit(namespace: str, name: str, ref: str) -> tuple[str, str, str] | None:
208
+ """(real_url, display_url, commit) of the tag, from the first spelling of
209
+ the namespace that has it, or None.
210
+
211
+ Asked with ``git ls-remote``, so nothing is cloned. An annotated tag is
212
+ named by the commit it points at, not by the tag object.
213
+ """
214
+ env = {
215
+ **os.environ,
216
+ **forge.git_auth_env(),
217
+ "GIT_TERMINAL_PROMPT": "0",
218
+ "LC_ALL": "C",
219
+ }
220
+ for spelling in namespace_spellings(namespace):
221
+ real, display = https_url(spelling, name)
222
+ try:
223
+ result = subprocess.run(
224
+ ["git", "ls-remote", real, f"refs/tags/{ref}", f"refs/tags/{ref}^{{}}"],
225
+ capture_output=True,
226
+ text=True,
227
+ timeout=PROBE_TIMEOUT,
228
+ env=env,
229
+ )
230
+ except (OSError, subprocess.TimeoutExpired):
231
+ continue
232
+ if result.returncode != 0:
233
+ continue
234
+ refs = dict(
235
+ reversed(line.split("\t", 1))
236
+ for line in result.stdout.splitlines()
237
+ if "\t" in line
238
+ )
239
+ commit = refs.get(f"refs/tags/{ref}^{{}}") or refs.get(f"refs/tags/{ref}")
240
+ if commit:
241
+ return real, display, commit
242
+ return None
243
+
244
+
245
+ def _pip_install_many(specs: list[str]) -> tuple[bool, str]:
246
+ """One pip run for every requirement in ``specs``.
247
+
248
+ What pip says is shown as it says it, redacted line by line, so a long
249
+ build is never silent. Returns whether it succeeded and its last lines.
250
+ """
251
+ auth = forge.git_auth_env()
252
+ cmd = [sys.executable, "-m", "pip", "install", "--progress-bar", "off", *specs]
253
+ tail: list[str] = []
254
+ try:
255
+ process = subprocess.Popen(
256
+ cmd,
257
+ stdout=subprocess.PIPE,
258
+ stderr=subprocess.STDOUT,
259
+ text=True,
260
+ env={**os.environ, **auth} if auth else None,
261
+ )
262
+ except OSError as exc:
263
+ return False, str(exc)
264
+ assert process.stdout is not None
265
+ for line in process.stdout:
266
+ line = _redacted(line.rstrip("\n"))
267
+ click.echo(f" {line}")
268
+ tail = [*tail, line][-TAIL_LINES:]
269
+ return process.wait() == 0, "\n".join(tail)
270
+
271
+
272
+ def _requirement(entry, data, pypi_only, asks_pypi):
273
+ """What one pip run installs for ``entry``.
274
+
275
+ Returns (spec, display, from_git), or None when it cannot be settled
276
+ without trying (PyPI could not be asked, or its tag could not be found),
277
+ which leaves the entry to the one-at-a-time install.
278
+ """
279
+ namespace, name, version, ref = _parse_feature_entry(entry)
280
+ dist = feature_distribution(data, name)
281
+ if not version:
282
+ return dist, dist, None
283
+ spec = f"{dist}=={version}"
284
+ if pypi_only:
285
+ return spec, spec, None
286
+ if not (ref and namespace):
287
+ return spec, spec, None
288
+ if not asks_pypi:
289
+ return None
290
+ served = _pypi_serves(dist, version)
291
+ if served is None:
292
+ return None
293
+ if served:
294
+ return spec, spec, None
295
+ found = _tag_commit(namespace, name, ref)
296
+ if found is None:
297
+ return None
298
+ real, display, commit = found
299
+ return (
300
+ f"{dist} @ git+{real}@{commit}",
301
+ f"git+{display}@{ref} ({commit[:12]})",
302
+ f"{name} {ref}",
303
+ )
304
+
305
+
306
+ def _install_together(features, data, pypi_only) -> list[str] | None:
307
+ """Install every feature in one pip run.
308
+
309
+ Returns what came from a git tag, or None when the features have to be
310
+ installed one at a time: one of them could not be settled in advance, or
311
+ the run failed and the one-at-a-time install says which feature broke it.
312
+ """
313
+ asks_pypi = _asks_pypi()
314
+ with ThreadPoolExecutor(max_workers=PROBE_WORKERS) as pool:
315
+ plan = list(
316
+ pool.map(
317
+ lambda entry: _requirement(entry, data, pypi_only, asks_pypi),
318
+ features,
319
+ )
320
+ )
321
+ if any(item is None for item in plan):
322
+ return None
323
+
324
+ for _spec, display, from_git in plan:
325
+ click.echo(f" {'git ' if from_git else 'pypi'} {display}")
326
+ click.echo()
327
+ ok, tail = _pip_install_many([spec for spec, _display, _git in plan])
328
+ click.echo()
329
+ if not ok:
330
+ click.secho(
331
+ " Installing them together failed. Installing them one at a time, "
332
+ "to say which one:",
333
+ fg="yellow",
334
+ )
335
+ click.echo()
336
+ return None
337
+ return [from_git for _spec, _display, from_git in plan if from_git]
338
+
339
+
157
340
  @click.command(
158
341
  "feature:pip-install",
159
342
  short_help="Install the declared features from PyPI, or from their git tag.",
@@ -204,15 +387,22 @@ def feature_pip_install(pypi_only):
204
387
 
205
388
  click.echo(f" Installing {len(features)} feature(s)...\n")
206
389
 
390
+ unpinned = [
391
+ _parse_feature_entry(entry)[1]
392
+ for entry in features
393
+ if not _parse_feature_entry(entry)[2]
394
+ ]
395
+ together = _install_together(features, data, pypi_only)
396
+ if together is not None:
397
+ _summary(features, unpinned, together, [])
398
+ return
399
+
207
400
  failed = []
208
- unpinned = []
209
401
  from_git = []
210
402
  for entry in features:
211
403
  namespace, name, version, ref = _parse_feature_entry(entry)
212
404
  dist = feature_distribution(data, name)
213
405
  spec = f"{dist}=={version}" if version else dist
214
- if not version:
215
- unpinned.append(name)
216
406
 
217
407
  click.echo(f" installing {spec}")
218
408
 
@@ -250,6 +440,11 @@ def feature_pip_install(pypi_only):
250
440
  )
251
441
  failed.append(spec)
252
442
 
443
+ _summary(features, unpinned, from_git, failed)
444
+
445
+
446
+ def _summary(features, unpinned, from_git, failed) -> None:
447
+ """What was installed from where, and what failed (which exits 1)."""
253
448
  click.echo()
254
449
  if unpinned:
255
450
  click.secho(
@@ -11,12 +11,20 @@
11
11
  # writes those pins, and the two files the image reads, splent.contracts.json
12
12
  # and docker/spl/. A clone of the repository builds the same image a release
13
13
  # published.
14
-
15
- # ── Stage 1: Builder ─────────────────────────────────────────────────────────
14
+ #
15
+ # The image is built in layers that change at different paces, and each is
16
+ # only rebuilt when what it is made from changes: the third-party packages
17
+ # when the lock does, the framework, the CLI and the features when their
18
+ # pins do, the product with every commit. pip keeps what it downloads and
19
+ # builds in a BuildKit cache, so a layer that is rebuilt takes from there
20
+ # what an earlier build already fetched or compiled. A server that updates
21
+ # the image pulls only the layers that changed.
22
+
23
+ # ── Stage 1: Third-party packages ────────────────────────────────────────────
16
24
  # Both stages on the same Debian release, so the packages the builder compiles
17
25
  # run on the runtime's libraries. The Python patch release follows the image,
18
26
  # which is how its security fixes arrive.
19
- FROM python:3.13-trixie AS builder
27
+ FROM python:3.13-trixie AS deps
20
28
  WORKDIR /workspace
21
29
 
22
30
  # System dependencies for building
@@ -39,58 +47,70 @@ RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /opt/node/bin/npm \
39
47
  && ln -s ../lib/node_modules/npm/bin/npx-cli.js /opt/node/bin/npx
40
48
  ENV PATH=/opt/node/bin:$PATH
41
49
 
42
- # The product. Dockerfile.{{ product_name }}.prod.dockerignore keeps the env
43
- # files (credentials), the development links and the deploy bundle out.
44
- COPY . {{ product_name }}/
45
-
46
- # Same protection for a builder that ignores that file (the classic builder,
47
- # DOCKER_BUILDKIT=0). Only the runtime stage is pushed, and it copies
48
- # /workspace from this stage, so the env files never reach a published image.
49
- RUN find {{ product_name }}/docker -maxdepth 1 -name '.env*' ! -name '*.example' \
50
- \( -type f -o -type l \) -delete
51
-
52
- # The SPL's UVL model. The framework orders features, and their migrations,
53
- # by its constraints and looks for it in .splent_cache/spls/. splent
54
- # product:build keeps the model pyproject.toml pins in docker/spl/, and this
55
- # puts it where the framework and the CLI look. Without it every boot warns
56
- # that features load in pyproject.toml order.
57
- RUN if [ -d {{ product_name }}/docker/spl ]; then \
58
- mkdir -p .splent_cache/spls && \
59
- cp -R {{ product_name }}/docker/spl/. .splent_cache/spls/; \
60
- fi
61
-
62
50
  ENV PIP_ROOT_USER_ACTION=ignore
63
51
 
64
52
  # Every third-party package at the version docker/requirements.lock pins, as
65
53
  # a constraint on every pip install below, so a clone of a release builds the
66
54
  # set the release built. splent product:build and product:release write the
67
55
  # lock by building with SPLENT_FRESH_LOCK=1, which resolves without it.
56
+ COPY docker/requirements.loc[k] /tmp/lock/
68
57
  ARG SPLENT_FRESH_LOCK=""
69
- RUN if [ -n "$SPLENT_FRESH_LOCK" ] || [ ! -f {{ product_name }}/docker/requirements.lock ]; then \
58
+ RUN if [ -n "$SPLENT_FRESH_LOCK" ] || [ ! -f /tmp/lock/requirements.lock ]; then \
70
59
  : > /tmp/constraints.txt; \
71
60
  else \
72
- cp {{ product_name }}/docker/requirements.lock /tmp/constraints.txt; \
61
+ cp /tmp/lock/requirements.lock /tmp/constraints.txt; \
73
62
  fi
74
63
  ENV PIP_CONSTRAINT=/tmp/constraints.txt
75
64
 
65
+ # The packages the lock pins, installed before anything SPLENT, so this layer
66
+ # only changes with the lock.
67
+ RUN --mount=type=cache,target=/root/.cache/pip \
68
+ if grep -q '^[^#[:space:]]' /tmp/constraints.txt; then \
69
+ pip install -r /tmp/constraints.txt; \
70
+ fi
71
+
72
+ # ── Stage 2: Pins ────────────────────────────────────────────────────────────
73
+ # What the builder installs from pyproject.toml, and nothing else of it: the
74
+ # requirements of [tool.splent.runtime] and [project].dependencies, and the
75
+ # features of [tool.splent]. Its version and settings change with every
76
+ # release, and the builder then reinstalls nothing, because these files are
77
+ # the same.
78
+ FROM python:3.13-trixie AS pins
79
+ WORKDIR /pins
80
+ COPY pyproject.toml /tmp/pyproject.toml
81
+
76
82
  # The framework, the CLI and the WSGI server, at the versions
77
- # [tool.splent.runtime] in pyproject.toml pins (a bare version means exactly
78
- # that version). splent product:build keeps the framework and the CLI at the
79
- # versions the product is built with, and a release of the product refuses
80
- # one PyPI does not serve.
83
+ # [tool.splent.runtime] pins (a bare version means exactly that version),
84
+ # then what the product itself depends on. splent product:build keeps the
85
+ # framework and the CLI at the versions the product is built with, and a
86
+ # release of the product refuses one PyPI does not serve.
81
87
  RUN python -c "import sys, tomllib; \
82
- runtime = tomllib.load(open('{{ product_name }}/pyproject.toml', 'rb')).get('tool', {}).get('splent', {}).get('runtime') \
88
+ data = tomllib.load(open('/tmp/pyproject.toml', 'rb')); \
89
+ runtime = data.get('tool', {}).get('splent', {}).get('runtime') \
83
90
  or sys.exit('[tool.splent.runtime] is missing from pyproject.toml. Run splent product:build.'); \
84
- print('\\n'.join(name + ('==' + str(spec) if str(spec)[:1].isdigit() else str(spec)) for name, spec in runtime.items()))" \
85
- > /tmp/runtime.txt \
86
- && pip install --no-cache-dir -r /tmp/runtime.txt
87
- RUN pip install --no-cache-dir ./{{ product_name }}
91
+ print('\\n'.join([name + ('==' + str(spec) if str(spec)[:1].isdigit() else str(spec)) for name, spec in runtime.items()] \
92
+ + list(data.get('project', {}).get('dependencies', []))))" \
93
+ > /pins/runtime.txt
94
+
95
+ # The features, with the PyPI names of those published under one of their
96
+ # own, as feature:pip-install reads them.
97
+ RUN python -c "import json, tomllib; \
98
+ splent = tomllib.load(open('/tmp/pyproject.toml', 'rb')).get('tool', {}).get('splent', {}); \
99
+ lines = ['[tool.splent]'] + [key + ' = ' + json.dumps(splent[key]) for key in ('features', 'features_dev', 'features_prod') if key in splent]; \
100
+ lines += ['[tool.splent.distributions]'] + [json.dumps(k) + ' = ' + json.dumps(v) for k, v in (splent.get('distributions') or {}).items()]; \
101
+ print('\\n'.join(lines))" \
102
+ > /pins/pyproject.toml
103
+
104
+ # ── Stage 3: Builder ─────────────────────────────────────────────────────────
105
+ FROM deps AS builder
106
+
107
+ # Everything SPLENT goes to its own prefix, pip's user site, rather than next
108
+ # to the third-party packages: the runtime stage copies each in its own layer.
109
+ # The product goes to another, since it changes with every commit.
110
+ ENV PYTHONUSERBASE=/opt/splent
111
+ ENV PYTHONPATH=/opt/product
112
+ ENV PATH=/opt/splent/bin:$PATH
88
113
 
89
- # Install the features with pip, from PyPI. This is the ONLY place features are
90
- # installed in a production image: scripts/00_install_features.sh (pip install -e
91
- # against the local checkout) belongs to the dev entrypoint and never runs here.
92
- # A feature declared without a version becomes a bare package name, so pip picks
93
- # whatever PyPI serves rather than the code in the workspace.
94
114
  ENV SPLENT_APP={{ product_name }}
95
115
  ENV WORKING_DIR=/workspace
96
116
  # The CLI's in-container detection keys on /.dockerenv, which the classic
@@ -112,11 +132,48 @@ ENV SPLENT_FORGE_HOST=${SPLENT_FORGE_HOST}
112
132
  ENV SPLENT_FORGE_SSH_HOST=${SPLENT_FORGE_SSH_HOST}
113
133
  ENV SPLENT_FORGE_SSH_PORT=${SPLENT_FORGE_SSH_PORT}
114
134
  ENV SPLENT_FORGE_API=${SPLENT_FORGE_API}
115
- RUN splent feature:pip-install
135
+
136
+ COPY --from=pins /pins/runtime.txt /tmp/runtime.txt
137
+ RUN --mount=type=cache,target=/root/.cache/pip \
138
+ pip install --user -r /tmp/runtime.txt
139
+
140
+ # Install the features with pip, from PyPI. This is the ONLY place features are
141
+ # installed in a production image: scripts/00_install_features.sh (pip install -e
142
+ # against the local checkout) belongs to the dev entrypoint and never runs here.
143
+ # A feature declared without a version becomes a bare package name, so pip picks
144
+ # whatever PyPI serves rather than the code in the workspace.
145
+ COPY --from=pins /pins/pyproject.toml {{ product_name }}/pyproject.toml
146
+ RUN --mount=type=cache,target=/root/.cache/pip \
147
+ PIP_USER=1 splent feature:pip-install
148
+
149
+ # The product. Dockerfile.{{ product_name }}.prod.dockerignore keeps the env
150
+ # files (credentials), the development links and the deploy bundle out.
151
+ COPY . {{ product_name }}/
152
+
153
+ # Same protection for a builder that ignores that file (the classic builder,
154
+ # DOCKER_BUILDKIT=0). Only the runtime stage is pushed, and it copies
155
+ # /workspace from this stage, so the env files never reach a published image.
156
+ RUN find {{ product_name }}/docker -maxdepth 1 -name '.env*' ! -name '*.example' \
157
+ \( -type f -o -type l \) -delete
158
+
159
+ # The SPL's UVL model. The framework orders features, and their migrations,
160
+ # by its constraints and looks for it in .splent_cache/spls/. splent
161
+ # product:build keeps the model pyproject.toml pins in docker/spl/, and this
162
+ # puts it where the framework and the CLI look. Without it every boot warns
163
+ # that features load in pyproject.toml order.
164
+ RUN if [ -d {{ product_name }}/docker/spl ]; then \
165
+ mkdir -p .splent_cache/spls && \
166
+ cp -R {{ product_name }}/docker/spl/. .splent_cache/spls/; \
167
+ fi
168
+
169
+ # What it depends on is already installed, with the runtime.
170
+ RUN --mount=type=cache,target=/root/.cache/pip \
171
+ pip install --no-deps --target /opt/product ./{{ product_name }}
116
172
 
117
173
  # The product's own frontend, when its package.json has a build script,
118
174
  # installed from its lock exactly as committed.
119
- RUN cd {{ product_name }} && \
175
+ RUN --mount=type=cache,target=/root/.npm \
176
+ cd {{ product_name }} && \
120
177
  if [ -f package.json ] && [ "$(npm pkg get scripts.build)" != "{}" ]; then \
121
178
  test -f package-lock.json || \
122
179
  { echo "package-lock.json is missing: run npm install in the product and commit it"; exit 1; }; \
@@ -134,7 +191,7 @@ RUN splent feature:compile --verify
134
191
  # The runtime stage copies /workspace, so they would ship otherwise.
135
192
  RUN rm -rf {{ product_name }}/node_modules {{ product_name }}/build
136
193
 
137
- # ── Stage 2: Runtime ─────────────────────────────────────────────────────────
194
+ # ── Stage 4: Runtime ─────────────────────────────────────────────────────────
138
195
  FROM python:3.13-slim-trixie
139
196
  WORKDIR /workspace
140
197
 
@@ -150,6 +207,9 @@ ENV PIP_ROOT_USER_ACTION=ignore
150
207
  ENV SPLENT_APP={{ product_name }}
151
208
  ENV WORKING_DIR=/workspace
152
209
  ENV SPLENT_CONTAINER=product
210
+ ENV PYTHONUSERBASE=/opt/splent
211
+ ENV PYTHONPATH=/opt/product
212
+ ENV PATH=/opt/splent/bin:$PATH
153
213
 
154
214
  # The user the app runs as. The image keeps root as its user so the entrypoint
155
215
  # can hand the named volumes to this one before it drops to it: see
@@ -160,9 +220,12 @@ RUN groupadd --system --gid 10001 splent \
160
220
  && mkdir -p /var/log/splent \
161
221
  && chown splent:splent /var/log/splent
162
222
 
163
- # Copy installed Python packages from builder
164
- COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
165
- COPY --from=builder /usr/local/bin /usr/local/bin
223
+ # The installed packages, one layer each: the third-party ones, then the
224
+ # framework, the CLI and the features, then the product.
225
+ COPY --from=deps /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
226
+ COPY --from=deps /usr/local/bin /usr/local/bin
227
+ COPY --from=builder /opt/splent /opt/splent
228
+ COPY --from=builder /opt/product /opt/product
166
229
 
167
230
  # Copy product source (includes compiled assets, templates, migrations,
168
231
  # entrypoints), owned by the app user, which writes its state file there.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.54.0
3
+ Version: 1.55.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
File without changes
File without changes
File without changes
File without changes