splent-cli 1.50.0__tar.gz → 1.51.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 (322) hide show
  1. {splent_cli-1.50.0/src/splent_cli.egg-info → splent_cli-1.51.0}/PKG-INFO +1 -1
  2. {splent_cli-1.50.0 → splent_cli-1.51.0}/pyproject.toml +1 -1
  3. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_deps.py +43 -3
  4. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_release.py +18 -0
  5. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/release/release_resume.py +27 -1
  6. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/frontend.py +4 -0
  7. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/registry.py +17 -11
  8. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/release.py +16 -1
  9. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/release_gate.py +2 -2
  10. splent_cli-1.51.0/src/splent_cli/services/source_mirror.py +356 -0
  11. {splent_cli-1.50.0 → splent_cli-1.51.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  12. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli.egg-info/SOURCES.txt +1 -0
  13. {splent_cli-1.50.0 → splent_cli-1.51.0}/COPYING +0 -0
  14. {splent_cli-1.50.0 → splent_cli-1.51.0}/COPYING.LESSER +0 -0
  15. {splent_cli-1.50.0 → splent_cli-1.51.0}/README.md +0 -0
  16. {splent_cli-1.50.0 → splent_cli-1.51.0}/setup.cfg +0 -0
  17. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/__init__.py +0 -0
  18. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/__main__.py +0 -0
  19. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/cli.py +0 -0
  20. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/__init__.py +0 -0
  21. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  22. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  23. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  24. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  25. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  26. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  27. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  28. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  29. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  30. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/__init__.py +0 -0
  31. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
  32. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  33. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_env.py +0 -0
  34. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_features.py +0 -0
  35. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_github.py +0 -0
  36. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  37. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_product.py +0 -0
  38. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  39. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  40. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_releases.py +0 -0
  41. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
  42. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  43. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  44. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  45. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  46. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/command/__init__.py +0 -0
  47. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/command/command_create.py +0 -0
  48. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/coverage.py +0 -0
  49. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_console.py +0 -0
  50. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  51. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  52. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_reset.py +0 -0
  53. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  54. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  55. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  56. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_status.py +0 -0
  57. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  58. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/doctor.py +0 -0
  59. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/env/env_list.py +0 -0
  60. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/env/env_set.py +0 -0
  61. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/env/env_show.py +0 -0
  62. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/export/__init__.py +0 -0
  63. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  64. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  65. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  66. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  67. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  68. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  69. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  70. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  71. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  72. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  73. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  74. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  75. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  76. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  77. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  78. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_frontend_init.py +0 -0
  79. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  80. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  81. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  82. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  83. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  84. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
  85. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  86. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  87. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  88. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_npm.py +0 -0
  89. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  90. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  91. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  92. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  93. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  94. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  95. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
  96. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  97. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  98. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  99. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_security.py +0 -0
  100. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  101. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  102. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
  103. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  104. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  105. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  106. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  107. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  108. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/lint.py +0 -0
  109. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/locust.py +0 -0
  110. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/marketplace/login.py +0 -0
  111. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
  112. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  113. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  114. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  115. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/__init__.py +0 -0
  116. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  117. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_build.py +0 -0
  118. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  119. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  120. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_config.py +0 -0
  121. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  122. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_console.py +0 -0
  123. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  124. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_create.py +0 -0
  125. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  126. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  127. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  128. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_down.py +0 -0
  129. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  130. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_env.py +0 -0
  131. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_frontend_audit.py +0 -0
  132. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_list.py +0 -0
  133. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  134. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  135. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_params.py +0 -0
  136. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_port.py +0 -0
  137. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  138. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  139. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  140. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_run.py +0 -0
  141. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_select.py +0 -0
  142. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  143. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  144. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  145. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_test.py +0 -0
  146. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_up.py +0 -0
  147. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/product/product_validate.py +0 -0
  148. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/release/__init__.py +0 -0
  149. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/release/release_core.py +0 -0
  150. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/selenium.py +0 -0
  151. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  152. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  153. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  154. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  155. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  156. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  157. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  158. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  159. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  160. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  161. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  162. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  163. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  164. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  165. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  166. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  167. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/storage/__init__.py +0 -0
  168. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/storage/storage_migrate.py +0 -0
  169. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  170. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  171. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/commands/version.py +0 -0
  172. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/__init__.py +0 -0
  173. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/compose.py +0 -0
  174. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/context.py +0 -0
  175. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/credentials.py +0 -0
  176. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/deploy_bundle.py +0 -0
  177. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/editable_refresh.py +0 -0
  178. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/image_registry.py +0 -0
  179. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/index_refresh.py +0 -0
  180. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/marketplace.py +0 -0
  181. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/marketplace_api.py +0 -0
  182. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/marketplace_url.py +0 -0
  183. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/neutral_app.py +0 -0
  184. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/own_commands.py +0 -0
  185. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/preflight.py +0 -0
  186. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/pypi_quota.py +0 -0
  187. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/release_dry_run.py +0 -0
  188. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/security_scan.py +0 -0
  189. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/spl_store.py +0 -0
  190. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/services/web_container.py +0 -0
  191. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
  192. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
  193. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
  194. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
  195. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
  196. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
  197. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
  198. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
  199. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
  200. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_frontend.npmrc.j2 +0 -0
  201. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_frontend_scripts.js.j2 +0 -0
  202. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
  203. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
  204. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
  205. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
  206. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
  207. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
  208. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
  209. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
  210. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
  211. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
  212. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
  213. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
  214. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
  215. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
  216. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
  217. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
  218. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
  219. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
  220. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
  221. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
  222. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
  223. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
  224. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
  225. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
  226. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
  227. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
  228. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/licenses/COPYING +0 -0
  229. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
  230. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
  231. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
  232. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
  233. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
  234. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
  235. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
  236. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
  237. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
  238. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
  239. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
  240. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
  241. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
  242. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
  243. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_400.html +0 -0
  244. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_401.html +0 -0
  245. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_404.html +0 -0
  246. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_500.html +0 -0
  247. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
  248. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
  249. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
  250. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
  251. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
  252. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_app.css +0 -0
  253. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_app.js +0 -0
  254. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
  255. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
  256. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
  257. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
  258. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
  259. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
  260. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
  261. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
  262. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
  263. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
  264. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
  265. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
  266. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
  267. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_own.css +0 -0
  268. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
  269. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
  270. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
  271. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/Dockerfile.frontend +0 -0
  272. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/package-lock.json +0 -0
  273. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/package.json +0 -0
  274. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/splent/build.js +0 -0
  275. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/splent/legacy.js +0 -0
  276. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/splent/lint.js +0 -0
  277. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/splent/plugin.js +0 -0
  278. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/splent/preset.js +0 -0
  279. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/toolchain/frontend/splent/singletons.json +0 -0
  280. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/__init__.py +0 -0
  281. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/archetype.py +0 -0
  282. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/cache_utils.py +0 -0
  283. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/ci_services.py +0 -0
  284. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/ci_workflow.py +0 -0
  285. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/command_loader.py +0 -0
  286. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  287. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/db_client.py +0 -0
  288. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/db_utils.py +0 -0
  289. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/decorators.py +0 -0
  290. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/deploy_freshness.py +0 -0
  291. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  292. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/feature_installer.py +0 -0
  293. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/feature_naming.py +0 -0
  294. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/feature_params.py +0 -0
  295. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/feature_requirements.py +0 -0
  296. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/feature_resolution.py +0 -0
  297. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/feature_utils.py +0 -0
  298. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/forge.py +0 -0
  299. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/frontend_check.py +0 -0
  300. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/frontend_licenses.py +0 -0
  301. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/git_url.py +0 -0
  302. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/integrity.py +0 -0
  303. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/io_utils.py +0 -0
  304. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/lifecycle.py +0 -0
  305. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/manifest.py +0 -0
  306. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/menu_keys.py +0 -0
  307. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/migration_lock.py +0 -0
  308. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/npm_semver.py +0 -0
  309. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/path_utils.py +0 -0
  310. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/proc.py +0 -0
  311. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/redact.py +0 -0
  312. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/refinement_facts.py +0 -0
  313. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/running_version.py +0 -0
  314. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/template_drift.py +0 -0
  315. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/toml_edit.py +0 -0
  316. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
  317. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/uvl_diagnosis.py +0 -0
  318. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli/utils/uvl_params.py +0 -0
  319. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  320. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  321. {splent_cli-1.50.0 → splent_cli-1.51.0}/src/splent_cli.egg-info/requires.txt +0 -0
  322. {splent_cli-1.50.0 → splent_cli-1.51.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.50.0
3
+ Version: 1.51.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.50.0"
7
+ version = "1.51.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"
@@ -83,6 +83,48 @@ def _parse_uvl_deps(uvl_path: str) -> tuple[dict[str, str], dict[str, set[str]]]
83
83
  # ---------------------------------------------------------------------------
84
84
 
85
85
 
86
+ def _bare_imports(source: str) -> set[str]:
87
+ """The features an import statement of SOURCE names outside a guard.
88
+
89
+ An import under a try whose handler tolerates ImportError, or under an
90
+ if that asks whether the product has the feature, is how a feature
91
+ reaches an optional one (feature:release reads it so for the contract),
92
+ and the product does without it. A source that does not parse is read
93
+ as text, every import counting.
94
+ """
95
+ import ast
96
+
97
+ from splent_cli.commands.feature.feature_release import (
98
+ _IMPORT_TOLERANT_HANDLERS,
99
+ _guarded_ranges,
100
+ _imported_names,
101
+ _in_ranges,
102
+ _module_string_constants,
103
+ _presence_guards,
104
+ )
105
+
106
+ try:
107
+ tree = ast.parse(source)
108
+ except (SyntaxError, ValueError):
109
+ return set(
110
+ re.findall(r"(?:from|import)\s+splent_io\.(splent_feature_\w+)", source)
111
+ )
112
+ guarded = _guarded_ranges(tree, _IMPORT_TOLERANT_HANDLERS)
113
+ present = _presence_guards(tree, _module_string_constants(tree))
114
+ found = set()
115
+ for node in ast.walk(tree):
116
+ if not isinstance(node, (ast.Import, ast.ImportFrom)):
117
+ continue
118
+ for item in _imported_names(node):
119
+ for short in re.findall(r"^splent_io\.splent_feature_(\w+?)(?:\.|$)", item):
120
+ if _in_ranges(node.lineno, guarded) or _in_ranges(
121
+ node.lineno, present.get(short, ())
122
+ ):
123
+ continue
124
+ found.add(f"splent_feature_{short}")
125
+ return found
126
+
127
+
86
128
  def _scan_feature_imports(
87
129
  feature_path: str, feature_name: str, all_packages: set[str]
88
130
  ) -> tuple[set[str], set[str]]:
@@ -130,9 +172,7 @@ def _scan_feature_imports(
130
172
  except (OSError, PermissionError):
131
173
  continue
132
174
 
133
- for match in re.findall(
134
- r"(?:from|import)\s+splent_io\.(splent_feature_\w+)", content
135
- ):
175
+ for match in _bare_imports(content):
136
176
  if match != feature_name and match in all_packages:
137
177
  python_imports.add(match)
138
178
 
@@ -28,6 +28,7 @@ from splent_cli.services import (
28
28
  image_registry,
29
29
  release,
30
30
  release_gate,
31
+ source_mirror,
31
32
  )
32
33
  from splent_cli.utils.proc import run, require_docker
33
34
 
@@ -385,6 +386,12 @@ def _left_out_flags(channels) -> list[str]:
385
386
  is_flag=True,
386
387
  help="Do not build or push the Docker image for this version.",
387
388
  )
389
+ @click.option(
390
+ "--no-mirror",
391
+ is_flag=True,
392
+ help="Do not publish to [tool.splent.release.source_mirror] now, for when it "
393
+ "is down. splent release:resume does it later.",
394
+ )
388
395
  @click.option(
389
396
  "--allow-unfinished",
390
397
  is_flag=True,
@@ -403,6 +410,7 @@ def product_release(
403
410
  no_forge,
404
411
  no_github,
405
412
  no_docker,
413
+ no_mirror,
406
414
  allow_unfinished,
407
415
  wait,
408
416
  ):
@@ -421,6 +429,7 @@ def product_release(
421
429
 
422
430
  _guard_all_features_pinned(product_path)
423
431
  _guard_deployable(str(context.workspace()), product, product_path)
432
+ mirror = None if no_mirror else _declared_source_mirror(product_path)
424
433
 
425
434
  # Determine org/repo for the semver wizard
426
435
  repo = release.get_repo_from_path(product_path)
@@ -502,7 +511,16 @@ def product_release(
502
511
  ),
503
512
  post_pypi_hook=_docker_hook,
504
513
  wait=wait,
514
+ source_mirror=mirror,
505
515
  )
506
516
 
507
517
 
518
+ def _declared_source_mirror(product_path: str):
519
+ try:
520
+ return source_mirror.declared(os.path.join(product_path, "pyproject.toml"))
521
+ except source_mirror.MirrorDeclarationError as e:
522
+ click.secho(f" error: {e}", fg="red")
523
+ raise SystemExit(1)
524
+
525
+
508
526
  cli_command = product_release
@@ -37,7 +37,7 @@ import tempfile
37
37
 
38
38
  import click
39
39
 
40
- from splent_cli.services import context, release, release_gate
40
+ from splent_cli.services import context, release, release_gate, source_mirror
41
41
  from splent_cli.utils import forge
42
42
  from splent_cli.utils.git_url import _non_interactive_env
43
43
 
@@ -539,6 +539,27 @@ def release_resume(target, assume_yes, no_pypi, no_forge, no_github, no_docker,
539
539
  if make_snapshot:
540
540
  steps.append(f"create the local snapshot for {tag}")
541
541
 
542
+ # The product's official repository, once the forge has what it copies.
543
+ mirror = None
544
+ if kind == "product" and release_gate.FORGE in channels:
545
+ try:
546
+ mirror = source_mirror.declared(pyproject)
547
+ if mirror:
548
+ mirror_steps = source_mirror.missing(mirror, path, tag)
549
+ steps.extend(mirror_steps)
550
+ mirror = mirror if mirror_steps else None
551
+ except source_mirror.MirrorDeclarationError as e:
552
+ click.secho(f" error: {e}", fg="red")
553
+ raise SystemExit(1)
554
+ except source_mirror.MirrorUnreachable as e:
555
+ stuck.append(
556
+ release_gate.ChannelStatus(
557
+ source_mirror.CHANNEL,
558
+ summary=f"could not ask {mirror.web_url} what it has ({e})",
559
+ )
560
+ )
561
+ mirror = None
562
+
542
563
  click.echo()
543
564
  if not steps:
544
565
  if stuck:
@@ -675,6 +696,11 @@ def release_resume(target, assume_yes, no_pypi, no_forge, no_github, no_docker,
675
696
  if failure:
676
697
  failures.append(f"the release for {tag} on the forge ({failure})")
677
698
 
699
+ if mirror:
700
+ failure = source_mirror.publish(mirror, path, tag)
701
+ if failure:
702
+ failures.append(f"{tag} on {mirror.web_url} ({failure})")
703
+
678
704
  if push_image:
679
705
  from splent_cli.commands.product.product_release import release_docker_image
680
706
 
@@ -740,6 +740,10 @@ def verify_assets(assets, *, lock=None, production: bool = False) -> list[str]:
740
740
  if dist.is_dir():
741
741
  for path in sorted(dist.rglob("*")):
742
742
  name = path.relative_to(dist).as_posix()
743
+ # pip compiles the .py a bundle ships to the browser (a pyodide
744
+ # wrapper) where it installs it: that bytecode is no file of it.
745
+ if "__pycache__" in path.parts:
746
+ continue
743
747
  if path.is_file() and name not in listed:
744
748
  problems.append(f"assets/dist/{name} is not in the manifest")
745
749
  if lock is not None and Path(lock).is_file():
@@ -402,27 +402,33 @@ def github_create_release(
402
402
  draft: bool = False,
403
403
  prerelease: bool = False,
404
404
  timeout: int = 30,
405
+ api_base: str | None = None,
406
+ generate_notes: bool = False,
405
407
  ) -> ReleaseResult:
406
- """Create a release for *tag* on the configured forge.
408
+ """Create a release for *tag* on the configured forge, or at *api_base*.
407
409
 
408
410
  Returns the outcome instead of raising for HTTP failures, so the caller can
409
411
  tell "already there" (idempotent rerun) from "refused" (a divergence, since
410
412
  the tag exists by the time this runs). Raises :class:`RegistryError` only
411
413
  when the forge cannot be reached at all.
414
+
415
+ ``generate_notes`` asks GitHub to append what changed since the last release
416
+ to *body*. Only GitHub knows it.
412
417
  """
413
- payload = json.dumps(
414
- {
415
- "tag_name": tag,
416
- "name": name,
417
- "body": body,
418
- "draft": draft,
419
- "prerelease": prerelease,
420
- }
421
- ).encode()
418
+ fields = {
419
+ "tag_name": tag,
420
+ "name": name,
421
+ "body": body,
422
+ "draft": draft,
423
+ "prerelease": prerelease,
424
+ }
425
+ if generate_notes:
426
+ fields["generate_release_notes"] = True
427
+ payload = json.dumps(fields).encode()
422
428
  headers = _github_headers(token)
423
429
  headers["Content-Type"] = "application/json"
424
430
  req = _forge_request(
425
- f"{forge.api_base()}/repos/{org}/{repo}/releases",
431
+ f"{api_base or forge.api_base()}/repos/{org}/{repo}/releases",
426
432
  data=payload,
427
433
  headers=headers,
428
434
  method="POST",
@@ -16,6 +16,7 @@ import tomllib
16
16
  import click
17
17
 
18
18
  from splent_cli.services import release_gate
19
+ from splent_cli.services import source_mirror as source_mirror_service
19
20
  from splent_cli.utils import forge
20
21
  from splent_cli.utils.git_url import _non_interactive_env
21
22
  from splent_cli.utils.io_utils import atomic_write
@@ -1619,6 +1620,7 @@ def run_release_pipeline(
1619
1620
  post_pypi_hook=None,
1620
1621
  wait: bool = False,
1621
1622
  neutral: bool = False,
1623
+ source_mirror=None,
1622
1624
  ):
1623
1625
  """Run the standard release pipeline.
1624
1626
 
@@ -1634,7 +1636,8 @@ def run_release_pipeline(
1634
1636
  7. Commit (local, revertible)
1635
1637
  8. Build and upload to PyPI
1636
1638
  9. Push the branch, the tag, and create the release on the git host
1637
- 10. (optional) post_pypi_hook, e.g. the snapshot or the Docker image
1639
+ 10. (optional) the same on source_mirror, the product's official repository
1640
+ 11. (optional) post_pypi_hook, e.g. the snapshot or the Docker image
1638
1641
 
1639
1642
  The order of 8 and 9 is the point. A PyPI version can never be replaced or
1640
1643
  removed; a tag that was never pushed costs nothing. Uploading first means a
@@ -1714,6 +1717,10 @@ def run_release_pipeline(
1714
1717
  deep=deep,
1715
1718
  allow_unfinished=allow_unfinished,
1716
1719
  )
1720
+ if source_mirror and to_forge:
1721
+ report.statuses.append(
1722
+ source_mirror_service.check(source_mirror, path, tag)
1723
+ )
1717
1724
  release_gate.render(report, header=header)
1718
1725
  if not report.ok:
1719
1726
  release_gate.refuse(report, resume_target=target)
@@ -1852,6 +1859,14 @@ def run_release_pipeline(
1852
1859
  )
1853
1860
  if failure:
1854
1861
  failures.append(f"the release for {tag} on the git host ({failure})")
1862
+ # Before the image: the version the image reaches production with
1863
+ # must already have its page where the app links to it.
1864
+ if source_mirror:
1865
+ failure = source_mirror_service.publish(source_mirror, path, tag)
1866
+ if failure:
1867
+ failures.append(f"{tag} on {source_mirror.web_url} ({failure})")
1868
+ else:
1869
+ accepted.append(source_mirror.web_url)
1855
1870
  else:
1856
1871
  create_and_push_tag(path, tag, push=False)
1857
1872
  click.secho(
@@ -195,7 +195,7 @@ def canonical_channel(name) -> str:
195
195
  return CHANNEL_ALIASES.get(key, key)
196
196
 
197
197
 
198
- _RELEASE_KEYS = {"channels", "registries", "mirrors"}
198
+ _RELEASE_KEYS = {"channels", "registries", "mirrors", "source_mirror"}
199
199
 
200
200
 
201
201
  def declared_channels(pyproject_path: str) -> tuple[str, ...]:
@@ -234,7 +234,7 @@ def declared_channels(pyproject_path: str) -> tuple[str, ...]:
234
234
  raise ChannelDeclarationError(
235
235
  "[tool.splent.release] has unknown key(s) "
236
236
  + ", ".join(unknown)
237
- + ". The keys are 'channels', 'registries' and 'mirrors', so this was "
237
+ + ". The keys are 'channels', 'registries', 'mirrors' and 'source_mirror', so this was "
238
238
  "ignored silently before."
239
239
  )
240
240
 
@@ -0,0 +1,356 @@
1
+ """A product's official repository, when it is not the forge it releases to.
2
+
3
+ A product lives on the forge, with its CI, its tags and its image registry, and
4
+ its public home can be somewhere else. A release has to reach that home too: the
5
+ page of a version is often linked from the app itself, so an image that reaches
6
+ production before its release exists there links to a 404.
7
+
8
+ [tool.splent.release.source_mirror]
9
+ url = "https://github.com/org/repo"
10
+ branches = ["main", "develop"]
11
+
12
+ A release moves every branch named to the commit of its tag, pushes the tag and
13
+ creates the release there, before the image goes out. Branches only ever move
14
+ forward: a mirror with commits this repository lacks refuses the release at the
15
+ gate, before anything changes.
16
+
17
+ The push goes over SSH, as the forge's does. The API call takes a token, from
18
+ SPLENT_SOURCE_MIRROR_TOKEN, GH_TOKEN, the GitHub CLI's login or GITHUB_TOKEN.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import dataclasses
24
+ import os
25
+ import shutil
26
+ import subprocess
27
+ import tomllib
28
+ from urllib.parse import urlparse
29
+
30
+ import click
31
+
32
+ from splent_cli.services import registry, release_gate
33
+ from splent_cli.utils.git_url import _non_interactive_env
34
+
35
+ CHANNEL = "mirror"
36
+ GITHUB_HOST = "github.com"
37
+ GIT_NETWORK_TIMEOUT = 600
38
+
39
+
40
+ class MirrorDeclarationError(ValueError):
41
+ """[tool.splent.release.source_mirror] does not say what a release needs."""
42
+
43
+
44
+ class MirrorUnreachable(Exception):
45
+ """The mirror could not be asked what it has."""
46
+
47
+
48
+ @dataclasses.dataclass(frozen=True)
49
+ class SourceMirror:
50
+ host: str
51
+ org: str
52
+ repo: str
53
+ branches: tuple[str, ...]
54
+ push_url: str
55
+
56
+ @property
57
+ def web_url(self) -> str:
58
+ return f"https://{self.host}/{self.org}/{self.repo}"
59
+
60
+ @property
61
+ def is_github(self) -> bool:
62
+ return self.host == GITHUB_HOST
63
+
64
+ @property
65
+ def api_base(self) -> str:
66
+ if self.is_github:
67
+ return "https://api.github.com"
68
+ return f"https://{self.host}/api/v1"
69
+
70
+
71
+ def declared(pyproject_path: str) -> SourceMirror | None:
72
+ """The mirror the product's pyproject.toml declares, or None."""
73
+ try:
74
+ with open(pyproject_path, "rb") as f:
75
+ data = tomllib.load(f)
76
+ except (OSError, tomllib.TOMLDecodeError):
77
+ return None
78
+ section = (
79
+ data.get("tool", {}).get("splent", {}).get("release", {}).get("source_mirror")
80
+ )
81
+ if section is None:
82
+ return None
83
+ if not isinstance(section, dict):
84
+ raise MirrorDeclarationError(
85
+ "[tool.splent.release.source_mirror] must be a table with a url."
86
+ )
87
+
88
+ url = section.get("url")
89
+ parsed = urlparse(url) if isinstance(url, str) else None
90
+ parts = [p for p in parsed.path.split("/") if p] if parsed else []
91
+ if not parsed or parsed.scheme != "https" or not parsed.hostname or len(parts) != 2:
92
+ raise MirrorDeclarationError(
93
+ "[tool.splent.release.source_mirror] url must be the repository's web "
94
+ 'address, for example url = "https://github.com/org/repo".'
95
+ )
96
+ org, repo = parts[0], parts[1].removesuffix(".git")
97
+
98
+ branches = section.get("branches", ["main"])
99
+ if (
100
+ not isinstance(branches, list)
101
+ or not branches
102
+ or not all(isinstance(b, str) and b.strip() for b in branches)
103
+ ):
104
+ raise MirrorDeclarationError(
105
+ "[tool.splent.release.source_mirror] branches must be a list of branch "
106
+ 'names, for example branches = ["main"].'
107
+ )
108
+ return SourceMirror(
109
+ host=parsed.hostname,
110
+ org=org,
111
+ repo=repo,
112
+ branches=tuple(dict.fromkeys(b.strip() for b in branches)),
113
+ push_url=f"git@{parsed.hostname}:{org}/{repo}.git",
114
+ )
115
+
116
+
117
+ def token(mirror: SourceMirror) -> str | None:
118
+ """The token for the mirror's API. Never printed."""
119
+ explicit = os.getenv("SPLENT_SOURCE_MIRROR_TOKEN") or os.getenv("GH_TOKEN")
120
+ if explicit:
121
+ return explicit
122
+ gh = shutil.which("gh")
123
+ if gh:
124
+ try:
125
+ result = subprocess.run(
126
+ [gh, "auth", "token", "--hostname", mirror.host],
127
+ capture_output=True,
128
+ text=True,
129
+ timeout=15,
130
+ )
131
+ except (OSError, subprocess.TimeoutExpired):
132
+ result = None
133
+ if result is not None and result.returncode == 0 and result.stdout.strip():
134
+ return result.stdout.strip()
135
+ return os.getenv("GITHUB_TOKEN") if mirror.is_github else None
136
+
137
+
138
+ def _git(path: str, *args: str) -> subprocess.CompletedProcess:
139
+ return subprocess.run(
140
+ ["git", *args],
141
+ cwd=path,
142
+ capture_output=True,
143
+ text=True,
144
+ env=_non_interactive_env(),
145
+ timeout=GIT_NETWORK_TIMEOUT,
146
+ )
147
+
148
+
149
+ def _remote_refs(mirror: SourceMirror, path: str) -> dict[str, str]:
150
+ """``{ref: sha}`` of the mirror's branches and tags."""
151
+ try:
152
+ result = _git(path, "ls-remote", "--heads", "--tags", mirror.push_url)
153
+ except (OSError, subprocess.TimeoutExpired) as e:
154
+ raise MirrorUnreachable(f"git ls-remote did not finish ({e})") from e
155
+ if result.returncode != 0:
156
+ raise MirrorUnreachable(result.stderr.strip() or "git ls-remote failed")
157
+ refs = {}
158
+ for line in result.stdout.splitlines():
159
+ sha, _, ref = line.partition("\t")
160
+ if ref:
161
+ refs[ref] = sha
162
+ return refs
163
+
164
+
165
+ def _have_commit(mirror: SourceMirror, path: str, sha: str, branch: str) -> bool:
166
+ """Whether *sha*, the tip of the mirror's *branch*, is here, fetching it if not."""
167
+ if _git(path, "cat-file", "-e", f"{sha}^{{commit}}").returncode == 0:
168
+ return True
169
+ _git(path, "fetch", "--no-tags", mirror.push_url, f"refs/heads/{branch}")
170
+ return _git(path, "cat-file", "-e", f"{sha}^{{commit}}").returncode == 0
171
+
172
+
173
+ def _is_ancestor(path: str, ancestor: str, descendant: str) -> bool:
174
+ result = _git(path, "merge-base", "--is-ancestor", ancestor, descendant)
175
+ return result.returncode == 0
176
+
177
+
178
+ def check(mirror: SourceMirror, path: str, tag: str) -> release_gate.ChannelStatus:
179
+ """Will the mirror take this release? Doubt counts as blocked, as in the gate."""
180
+ st = release_gate.ChannelStatus(CHANNEL)
181
+
182
+ def blocked(summary: str, *hints: str) -> release_gate.ChannelStatus:
183
+ st.summary = f"blocked, {summary}"
184
+ st.hints.extend(hints)
185
+ return st
186
+
187
+ secret = token(mirror)
188
+ if not secret:
189
+ return blocked(
190
+ f"no token for {mirror.host}",
191
+ "Set SPLENT_SOURCE_MIRROR_TOKEN, or log in with the GitHub CLI.",
192
+ )
193
+ try:
194
+ repository = registry.github_json(
195
+ f"{mirror.api_base}/repos/{mirror.org}/{mirror.repo}", secret
196
+ )
197
+ except (registry.RegistryError, ValueError) as e:
198
+ return blocked(f"{mirror.web_url} could not be asked ({e})")
199
+ if repository is None:
200
+ return blocked(f"{mirror.web_url} does not exist, or this token cannot see it")
201
+ if repository.get("archived"):
202
+ return blocked(f"{mirror.web_url} is archived")
203
+ if (repository.get("permissions") or {}).get("push") is False:
204
+ return blocked(f"this token cannot push to {mirror.web_url}")
205
+
206
+ try:
207
+ refs = _remote_refs(mirror, path)
208
+ except MirrorUnreachable as e:
209
+ return blocked(
210
+ f"git cannot reach {mirror.push_url} ({e})",
211
+ "The push goes over SSH, with the key this machine uses for the host.",
212
+ )
213
+ if f"refs/tags/{tag}" in refs:
214
+ return blocked(
215
+ f"{tag} is already there",
216
+ "Finish that version with splent release:resume, or release another.",
217
+ )
218
+ behind = [
219
+ branch
220
+ for branch in mirror.branches
221
+ if (sha := refs.get(f"refs/heads/{branch}"))
222
+ and not (
223
+ _have_commit(mirror, path, sha, branch) and _is_ancestor(path, sha, "HEAD")
224
+ )
225
+ ]
226
+ if behind:
227
+ names = ", ".join(behind)
228
+ return blocked(
229
+ f"{names} on {mirror.web_url} has commits this repository lacks",
230
+ "A branch only moves forward. Bring them in with "
231
+ f"git fetch {mirror.push_url} {behind[0]} && git merge FETCH_HEAD "
232
+ "(-s ours when this repository's tree is the one that stands).",
233
+ )
234
+ st.ok = True
235
+ st.summary = f"ready, {names_of(mirror)} and the release on {mirror.web_url}"
236
+ return st
237
+
238
+
239
+ def names_of(mirror: SourceMirror) -> str:
240
+ return ", ".join(mirror.branches)
241
+
242
+
243
+ def _branches_to_move(
244
+ mirror: SourceMirror, path: str, commit: str, refs: dict[str, str]
245
+ ) -> list[str]:
246
+ """The branches that do not hold *commit* yet."""
247
+ return [
248
+ branch
249
+ for branch in mirror.branches
250
+ if not (
251
+ (sha := refs.get(f"refs/heads/{branch}"))
252
+ and _have_commit(mirror, path, sha, branch)
253
+ and _is_ancestor(path, commit, sha)
254
+ )
255
+ ]
256
+
257
+
258
+ def _local_commit(path: str, tag: str) -> str | None:
259
+ result = _git(
260
+ path, "rev-parse", "--verify", "--quiet", f"refs/tags/{tag}^{{commit}}"
261
+ )
262
+ return result.stdout.strip() if result.returncode == 0 else None
263
+
264
+
265
+ def missing(mirror: SourceMirror, path: str, tag: str) -> list[str]:
266
+ """What the mirror still lacks of *tag*, as steps to show. Empty when complete.
267
+
268
+ Raises :class:`MirrorUnreachable` when it cannot tell.
269
+ """
270
+ refs = _remote_refs(mirror, path)
271
+ commit = _local_commit(path, tag)
272
+ steps = []
273
+ if commit is None:
274
+ move = list(mirror.branches)
275
+ else:
276
+ move = _branches_to_move(mirror, path, commit, refs)
277
+ if move:
278
+ steps.append(f"move {', '.join(move)} on {mirror.web_url} to {tag}")
279
+ if f"refs/tags/{tag}" not in refs:
280
+ steps.append(f"push the tag {tag} to {mirror.web_url}")
281
+ try:
282
+ existing = registry.github_json(
283
+ f"{mirror.api_base}/repos/{mirror.org}/{mirror.repo}/releases/tags/{tag}",
284
+ token(mirror),
285
+ )
286
+ except (registry.RegistryError, ValueError) as e:
287
+ raise MirrorUnreachable(str(e)) from e
288
+ if existing is None:
289
+ steps.append(f"create the release for {tag} on {mirror.web_url}")
290
+ return steps
291
+
292
+
293
+ def _release_failure(result, mirror: SourceMirror) -> str:
294
+ if result.rate_limited:
295
+ return f"{mirror.host} is rate limiting this token (HTTP {result.status})"
296
+ if result.status == 401:
297
+ return f"{mirror.host} rejected the token (HTTP 401)"
298
+ if result.status == 403:
299
+ return f"this token cannot create releases on {mirror.web_url} (HTTP 403)"
300
+ return f"{mirror.host} answered HTTP {result.status} {result.detail}".strip()
301
+
302
+
303
+ def publish(mirror: SourceMirror, path: str, tag: str) -> str | None:
304
+ """Bring *tag* to the mirror: branches, tag and release.
305
+
306
+ Does only what is missing, so running it again finishes it. Returns ``None``
307
+ on success and the reason on failure, for the caller to report once the
308
+ channels that can still finish have.
309
+ """
310
+ commit = _local_commit(path, tag)
311
+ if commit is None:
312
+ return f"there is no {tag} here to publish"
313
+ try:
314
+ refs = _remote_refs(mirror, path)
315
+ move = _branches_to_move(mirror, path, commit, refs)
316
+ refspecs = [f"{commit}:refs/heads/{branch}" for branch in move]
317
+ if f"refs/tags/{tag}" not in refs:
318
+ refspecs.append(f"refs/tags/{tag}:refs/tags/{tag}")
319
+ if refspecs:
320
+ # All of it or none of it: a tag without its branches, or the
321
+ # reverse, is the state this exists to avoid.
322
+ pushed = _git(path, "push", "--atomic", mirror.push_url, *refspecs)
323
+ if pushed.returncode != 0:
324
+ return pushed.stderr.strip() or "git push failed"
325
+ except (MirrorUnreachable, OSError, subprocess.TimeoutExpired) as e:
326
+ return str(e)
327
+ if move:
328
+ click.echo(f" mirror {names_of(mirror)} at {tag} on {mirror.web_url}")
329
+
330
+ secret = token(mirror)
331
+ if not secret:
332
+ return f"no token for {mirror.host}"
333
+ try:
334
+ result = registry.github_create_release(
335
+ mirror.org,
336
+ mirror.repo,
337
+ tag=tag,
338
+ name=f"{mirror.repo} {tag}",
339
+ body=(
340
+ "Released with [SPLENT](https://www.splent.io). The `deploy/` "
341
+ "directory of this tag holds what a server needs to run this "
342
+ "version.\n"
343
+ ),
344
+ token=secret,
345
+ api_base=mirror.api_base,
346
+ generate_notes=mirror.is_github,
347
+ )
348
+ except registry.RegistryError as e:
349
+ return f"{mirror.host} could not be reached ({e})"
350
+ if result.created:
351
+ click.echo(f" mirror release created {result.url or tag}")
352
+ return None
353
+ if result.already_exists:
354
+ click.secho(f" mirror release for {tag} already exists, kept", fg="yellow")
355
+ return None
356
+ return _release_failure(result, mirror)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.50.0
3
+ Version: 1.51.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
@@ -187,6 +187,7 @@ src/splent_cli/services/release.py
187
187
  src/splent_cli/services/release_dry_run.py
188
188
  src/splent_cli/services/release_gate.py
189
189
  src/splent_cli/services/security_scan.py
190
+ src/splent_cli/services/source_mirror.py
190
191
  src/splent_cli/services/spl_store.py
191
192
  src/splent_cli/services/web_container.py
192
193
  src/splent_cli/templates/feature/feature_.gitignore.j2
File without changes