splent-cli 1.27.1__tar.gz → 1.28.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 (201) hide show
  1. {splent_cli-1.27.1/src/splent_cli.egg-info → splent_cli-1.28.0}/PKG-INFO +2 -2
  2. {splent_cli-1.27.1 → splent_cli-1.28.0}/pyproject.toml +6 -3
  3. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_product.py +99 -5
  4. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_console.py +5 -6
  5. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_dump.py +5 -6
  6. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_restore.py +7 -12
  7. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_rollback.py +28 -3
  8. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_seed.py +7 -3
  9. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_add.py +11 -9
  10. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_attach.py +27 -21
  11. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_compile.py +148 -4
  12. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_detach.py +3 -3
  13. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_order.py +15 -7
  14. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_outdated.py +15 -19
  15. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_pin.py +3 -3
  16. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_refine.py +340 -52
  17. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_release.py +105 -15
  18. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_remove.py +3 -3
  19. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_rename.py +3 -3
  20. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_test.py +18 -0
  21. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_unlock.py +11 -5
  22. splent_cli-1.28.0/src/splent_cli/commands/feature/feature_upgrade.py +757 -0
  23. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_auto_require.py +5 -8
  24. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_build.py +381 -5
  25. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_configure.py +3 -3
  26. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_create.py +4 -2
  27. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_deploy.py +44 -6
  28. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_derive.py +2 -1
  29. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_env.py +2 -6
  30. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_release.py +3 -1
  31. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_up.py +20 -11
  32. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_validate.py +198 -14
  33. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/credentials.py +1 -1
  34. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/preflight.py +40 -0
  35. splent_cli-1.28.0/src/splent_cli/utils/db_client.py +38 -0
  36. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/io_utils.py +5 -1
  37. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/lifecycle.py +4 -4
  38. splent_cli-1.28.0/src/splent_cli/utils/refinement_facts.py +1651 -0
  39. splent_cli-1.28.0/src/splent_cli/utils/toml_edit.py +765 -0
  40. splent_cli-1.28.0/src/splent_cli/utils/upgrade_plan.py +1054 -0
  41. {splent_cli-1.27.1 → splent_cli-1.28.0/src/splent_cli.egg-info}/PKG-INFO +2 -2
  42. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli.egg-info/SOURCES.txt +5 -1
  43. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli.egg-info/requires.txt +1 -1
  44. splent_cli-1.27.1/src/splent_cli/commands/feature/feature_upgrade.py +0 -336
  45. {splent_cli-1.27.1 → splent_cli-1.28.0}/LICENSE +0 -0
  46. {splent_cli-1.27.1 → splent_cli-1.28.0}/README.md +0 -0
  47. {splent_cli-1.27.1 → splent_cli-1.28.0}/setup.cfg +0 -0
  48. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/__init__.py +0 -0
  49. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/__main__.py +0 -0
  50. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/cli.py +0 -0
  51. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/__init__.py +0 -0
  52. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  53. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  54. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  55. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  56. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  57. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  58. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  59. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  60. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  61. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/__init__.py +0 -0
  62. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
  63. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  64. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  65. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_env.py +0 -0
  66. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_features.py +0 -0
  67. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_github.py +0 -0
  68. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  69. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  70. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  71. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/check/check_releases.py +0 -0
  72. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  73. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  74. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  75. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  76. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/command/__init__.py +0 -0
  77. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/command/command_create.py +0 -0
  78. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/coverage.py +0 -0
  79. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  80. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_reset.py +0 -0
  81. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_status.py +0 -0
  82. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  83. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/doctor.py +0 -0
  84. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/env/env_list.py +0 -0
  85. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/env/env_set.py +0 -0
  86. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/env/env_show.py +0 -0
  87. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/export/__init__.py +0 -0
  88. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  89. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  90. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  91. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  92. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  93. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
  94. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  95. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  96. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  97. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  98. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  99. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  100. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  101. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  102. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  103. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  104. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
  105. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  106. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  107. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  108. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  109. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  110. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  111. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  112. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  113. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  114. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  115. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  116. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/lint.py +0 -0
  117. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/locust.py +0 -0
  118. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/login.py +0 -0
  119. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
  120. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  121. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  122. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  123. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/__init__.py +0 -0
  124. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  125. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  126. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_config.py +0 -0
  127. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_console.py +0 -0
  128. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  129. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  130. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_down.py +0 -0
  131. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  132. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_list.py +0 -0
  133. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  134. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  135. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_port.py +0 -0
  136. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  137. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  138. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  139. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_run.py +0 -0
  140. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_select.py +0 -0
  141. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  142. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  143. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  144. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/product/product_test.py +0 -0
  145. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/release/__init__.py +0 -0
  146. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/release/release_core.py +0 -0
  147. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/release/release_resume.py +0 -0
  148. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/selenium.py +0 -0
  149. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  150. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  151. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  152. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  153. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  154. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  155. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  156. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  157. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  158. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  159. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  160. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  161. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  162. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  163. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  164. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  165. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  166. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  167. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/commands/version.py +0 -0
  168. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/__init__.py +0 -0
  169. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/compose.py +0 -0
  170. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/context.py +0 -0
  171. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/index_refresh.py +0 -0
  172. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/marketplace.py +0 -0
  173. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/marketplace_api.py +0 -0
  174. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/marketplace_url.py +0 -0
  175. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/pypi_quota.py +0 -0
  176. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/registry.py +0 -0
  177. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/release.py +0 -0
  178. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/release_dry_run.py +0 -0
  179. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/release_gate.py +0 -0
  180. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/services/spl_store.py +0 -0
  181. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/__init__.py +0 -0
  182. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/archetype.py +0 -0
  183. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/cache_utils.py +0 -0
  184. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/command_loader.py +0 -0
  185. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  186. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/db_utils.py +0 -0
  187. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/decorators.py +0 -0
  188. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  189. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/feature_installer.py +0 -0
  190. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/feature_utils.py +0 -0
  191. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/forge.py +0 -0
  192. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/git_url.py +0 -0
  193. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/integrity.py +0 -0
  194. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/manifest.py +0 -0
  195. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/path_utils.py +0 -0
  196. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/proc.py +0 -0
  197. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/redact.py +0 -0
  198. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli/utils/template_drift.py +0 -0
  199. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  200. {splent_cli-1.27.1 → splent_cli-1.28.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  201. {splent_cli-1.27.1 → splent_cli-1.28.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.27.1
3
+ Version: 1.28.0
4
4
  Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
5
  Author-email: DiversoLab <diversolab@us.es>
6
6
  Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
@@ -14,7 +14,7 @@ Requires-Dist: packaging>=24.0
14
14
  Requires-Dist: python-dotenv>=1.0
15
15
  Requires-Dist: pyyaml>=6.0
16
16
  Requires-Dist: requests>=2.31.0
17
- Requires-Dist: splent_framework<2,>=1.16.1
17
+ Requires-Dist: splent_framework<2,>=1.18.0
18
18
  Requires-Dist: tomli_w>=1.0.0
19
19
  Provides-Extra: dev
20
20
  Requires-Dist: setuptools==80.3.1; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.27.1"
7
+ version = "1.28.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"
@@ -23,8 +23,11 @@ dependencies = [
23
23
  "pyyaml>=6.0",
24
24
  "requests>=2.31.0",
25
25
  # The CLI drives the framework (paths, feature order, migrations, the
26
- # app factory) and only works with the same major version of it.
27
- "splent_framework>=1.16.1,<2",
26
+ # app factory) and only works with the same major version of it. 1.18.0
27
+ # resolves a feature to the copy feature:test names (SPLENT_FEATURE_SOURCES)
28
+ # and brings the production defaults the deploy templates explain; 1.17.0
29
+ # the refinement rules product:validate shares with the boot.
30
+ "splent_framework>=1.18.0,<2",
28
31
  "tomli_w>=1.0.0",
29
32
  ]
30
33
 
@@ -6,9 +6,11 @@ Validates that the active product is correctly wired:
6
6
  2. Feature symlinks resolve to real directories
7
7
  3. Config overwrites are flagged
8
8
  4. Features that loaded but registered no blueprints are warned
9
+ 5. Product templates that link a static file that does not exist are warned
9
10
  """
10
11
 
11
12
  import os
13
+ import re
12
14
  import tomllib
13
15
 
14
16
  import click
@@ -304,6 +306,94 @@ def _check_blueprints(counters):
304
306
  counters["ok"] += 1
305
307
 
306
308
 
309
+ # ── Static links ─────────────────────────────────────────────────────────────
310
+
311
+ # A literal filename only. One built at render time ('img/' ~ name) cannot be
312
+ # checked without rendering, so it is left alone.
313
+ _STATIC_LINK = re.compile(
314
+ r"""url_for\(\s*['"]static['"]\s*,\s*filename\s*=\s*['"]([^'"{}~]+)['"]\s*\)"""
315
+ )
316
+ # The icon a broken icon link should point at instead, best first: the
317
+ # scaffold's favicon.svg, then what products that replaced it tend to ship.
318
+ _FAVICONS = (
319
+ ("favicon.svg", "image/svg+xml"),
320
+ ("favicon.ico", "image/x-icon"),
321
+ ("favicon-32.png", "image/png"),
322
+ ("favicon.png", "image/png"),
323
+ )
324
+
325
+
326
+ def _favicon_line(static: str) -> str | None:
327
+ for filename, mime in _FAVICONS:
328
+ if os.path.isfile(os.path.join(static, filename)):
329
+ return (
330
+ f'<link rel="icon" type="{mime}" '
331
+ f"href=\"{{{{ url_for('static', filename='{filename}') }}}}\">"
332
+ )
333
+ return None
334
+
335
+
336
+ def _broken_static_links(product_path: str, product: str) -> list[tuple]:
337
+ """(template, line number, line, filename) for each link to a missing file.
338
+
339
+ Only the product's own templates and the app's static folder, which is
340
+ what url_for('static', ...) serves. A feature's templates link their
341
+ blueprint's static folder and ship with the feature.
342
+ """
343
+ package = os.path.join(product_path, "src", product)
344
+ templates = os.path.join(package, "templates")
345
+ static = os.path.join(package, "static")
346
+ broken = []
347
+ if not os.path.isdir(templates):
348
+ return broken
349
+ for root, dirs, files in os.walk(templates):
350
+ dirs.sort()
351
+ for name in sorted(files):
352
+ if not name.endswith((".html", ".j2", ".jinja", ".jinja2")):
353
+ continue
354
+ path = os.path.join(root, name)
355
+ try:
356
+ with open(path, encoding="utf-8") as fh:
357
+ lines = fh.read().splitlines()
358
+ except (OSError, UnicodeDecodeError):
359
+ continue
360
+ rel = os.path.relpath(path, package).replace(os.sep, "/")
361
+ for number, line in enumerate(lines, start=1):
362
+ for filename in _STATIC_LINK.findall(line):
363
+ target = os.path.join(static, *filename.strip("/").split("/"))
364
+ if not os.path.isfile(target):
365
+ broken.append((rel, number, line.strip(), filename))
366
+ return broken
367
+
368
+
369
+ def _check_static_links(product_path, product, counters):
370
+ """Warn about product templates linking a static file that is not there."""
371
+ click.echo()
372
+ click.secho(" Static links", bold=True, fg="cyan")
373
+ click.echo()
374
+
375
+ broken = _broken_static_links(product_path, product)
376
+ if not broken:
377
+ click.echo(" All static links in the product templates resolve.")
378
+ counters["ok"] += 1
379
+ return
380
+
381
+ favicon_line = _favicon_line(os.path.join(product_path, "src", product, "static"))
382
+ for rel, number, line, filename in broken:
383
+ click.echo(
384
+ click.style(" ⚠ ", fg="yellow")
385
+ + f"{rel} line {number} links static/{filename}, which does not exist."
386
+ )
387
+ if favicon_line and re.search(r"rel=['\"](shortcut )?icon['\"]", line):
388
+ click.echo(f" Replace that line with {favicon_line}")
389
+ else:
390
+ click.echo(
391
+ f" Add the file under src/{product}/static or point the link "
392
+ "at a file that exists there."
393
+ )
394
+ counters["warn"] += len(broken)
395
+
396
+
307
397
  # ── Command ──────────────────────────────────────────────────────────────────
308
398
 
309
399
 
@@ -329,9 +419,11 @@ def check_product():
329
419
  click.secho(" pyproject.toml not found", fg="red")
330
420
  raise SystemExit(1)
331
421
 
422
+ _check_static_links(product_path, product, counters)
423
+
332
424
  if not features:
333
425
  click.secho(" No features declared.", fg="yellow")
334
- click.echo()
426
+ _print_summary(counters)
335
427
  return
336
428
 
337
429
  # Report stale contracts before reading them. A check never rewrites a
@@ -345,8 +437,13 @@ def check_product():
345
437
  _check_symlinks(product_path, features, counters)
346
438
  _check_config_overwrites(counters)
347
439
  _check_blueprints(counters)
440
+ _print_summary(counters)
441
+
442
+ if counters["fail"]:
443
+ raise SystemExit(1)
348
444
 
349
- # Summary
445
+
446
+ def _print_summary(counters):
350
447
  click.echo()
351
448
  parts = []
352
449
  if counters["ok"]:
@@ -358,8 +455,5 @@ def check_product():
358
455
  click.echo(" " + ", ".join(parts))
359
456
  click.echo()
360
457
 
361
- if counters["fail"]:
362
- raise SystemExit(1)
363
-
364
458
 
365
459
  cli_command = check_product
@@ -3,6 +3,7 @@ from dotenv import load_dotenv
3
3
  import os
4
4
 
5
5
  from splent_cli.services import context
6
+ from splent_cli.utils import db_client
6
7
  from splent_cli.utils.proc import run, require_tool
7
8
 
8
9
 
@@ -38,16 +39,14 @@ def db_console():
38
39
  )
39
40
  raise SystemExit(1)
40
41
 
41
- require_tool(
42
- "mysql",
43
- "Install the MariaDB/MySQL client (e.g. 'mariadb-client') and ensure it is on your PATH.",
44
- )
42
+ tool = db_client.client_tool()
43
+ require_tool(tool, db_client.INSTALL_HINT)
45
44
 
46
45
  # The password goes in MYSQL_PWD, as in db:dump and db:restore, so it
47
46
  # never shows up in the process list.
48
47
  result = run(
49
48
  [
50
- "mysql",
49
+ tool,
51
50
  f"-h{mariadb_hostname}",
52
51
  f"-u{mariadb_user}",
53
52
  mariadb_database,
@@ -57,7 +56,7 @@ def db_console():
57
56
  )
58
57
  if result.returncode != 0:
59
58
  click.secho(
60
- f"❌ Could not open the MariaDB console (mysql exited {result.returncode}).",
59
+ f"❌ Could not open the MariaDB console ({tool} exited {result.returncode}).",
61
60
  fg="red",
62
61
  )
63
62
  click.secho(
@@ -8,6 +8,7 @@ import tempfile
8
8
  from datetime import datetime
9
9
 
10
10
  from splent_cli.services import context
11
+ from splent_cli.utils import db_client
11
12
  from splent_cli.utils.proc import run, require_tool
12
13
 
13
14
  # A dump below this size cannot be a real database export (even an empty
@@ -133,10 +134,8 @@ def db_dump(filename, gzip_output, retention):
133
134
  "Create it first or choose another path."
134
135
  )
135
136
 
136
- require_tool(
137
- "mysqldump",
138
- "Install the MariaDB/MySQL client tools (provides 'mysqldump').",
139
- )
137
+ dump_tool = db_client.dump_tool()
138
+ require_tool(dump_tool, db_client.INSTALL_HINT)
140
139
 
141
140
  env = {**os.environ, "MYSQL_PWD": mariadb_password or ""}
142
141
 
@@ -153,7 +152,7 @@ def db_dump(filename, gzip_output, retention):
153
152
  with os.fdopen(fd, "wb") as out:
154
153
  run(
155
154
  [
156
- "mysqldump",
155
+ dump_tool,
157
156
  # Reliability flags. --single-transaction takes a
158
157
  # consistent snapshot without locking tables, --quick
159
158
  # streams rows instead of buffering whole tables in
@@ -169,7 +168,7 @@ def db_dump(filename, gzip_output, retention):
169
168
  stdout=out,
170
169
  text=False,
171
170
  env=env,
172
- tool_hint="Install the MariaDB/MySQL client tools (provides 'mysqldump').",
171
+ tool_hint=db_client.INSTALL_HINT,
173
172
  )
174
173
 
175
174
  # A truncated dump restores as an empty or partial database, which is
@@ -10,6 +10,7 @@ from datetime import datetime
10
10
  from dotenv import load_dotenv
11
11
 
12
12
  from splent_cli.services import context
13
+ from splent_cli.utils import db_client
13
14
  from splent_cli.utils.proc import run
14
15
 
15
16
  # Same-second collisions get _1, _2, ... ; this many tries means something
@@ -108,7 +109,7 @@ def db_restore(filename, yes):
108
109
  tmp_sql_path = None
109
110
 
110
111
  try:
111
- # Accept gzip-compressed dumps transparently. mysql needs a plain SQL
112
+ # Accept gzip-compressed dumps transparently. The client needs a plain SQL
112
113
  # stream on stdin, so decompress to a temp file next to the dump and
113
114
  # feed that instead. Doing this before the pre-restore backup means a
114
115
  # corrupt archive aborts the command before anything else happens.
@@ -140,7 +141,7 @@ def db_restore(filename, yes):
140
141
  with os.fdopen(backup_fd, "wb") as backup_out:
141
142
  run(
142
143
  [
143
- "mysqldump",
144
+ db_client.dump_tool(),
144
145
  # Same reliability flags as db:dump, so the safety-net
145
146
  # backup is itself a consistent, restorable dump.
146
147
  "--single-transaction",
@@ -152,13 +153,10 @@ def db_restore(filename, yes):
152
153
  ],
153
154
  stdout=backup_out,
154
155
  env=env,
155
- tool_hint=(
156
- "Install the MariaDB/MySQL client tools (provides 'mysqldump'), "
157
- "e.g. 'mysql-client' / 'mariadb-client'."
158
- ),
156
+ tool_hint=db_client.INSTALL_HINT,
159
157
  )
160
158
  except click.ClickException:
161
- # mysqldump missing/failed (often a connection failure): do not proceed
159
+ # The dump tool missing or failed (often a connection failure): do not proceed
162
160
  # to overwrite the live database without a safety net.
163
161
  # Only ever the file this run created (O_EXCL above).
164
162
  if backup_filename and os.path.exists(backup_filename):
@@ -176,13 +174,10 @@ def db_restore(filename, yes):
176
174
  try:
177
175
  with open(restore_path, "rb") as sql_file:
178
176
  run(
179
- ["mysql", f"-h{host}", f"-u{user}", database],
177
+ [db_client.client_tool(), f"-h{host}", f"-u{user}", database],
180
178
  stdin=sql_file,
181
179
  env=env,
182
- tool_hint=(
183
- "Install the MariaDB/MySQL client tools (provides 'mysql'), "
184
- "e.g. 'mysql-client' / 'mariadb-client'."
185
- ),
180
+ tool_hint=db_client.INSTALL_HINT,
186
181
  )
187
182
  click.secho(f"✅ Database restored from: {filename}", fg="green")
188
183
  except click.ClickException as e:
@@ -14,6 +14,7 @@ from splent_cli.commands.database.db_upgrade import (
14
14
  from splent_cli.utils.decorators import requires_db
15
15
  from splent_cli.services import context
16
16
  from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
17
+ from splent_cli.utils.refinement_facts import read_refines
17
18
  from splent_framework.managers.migration_manager import MigrationManager
18
19
  from splent_framework.managers.feature_order import FeatureLoadOrderResolver
19
20
  from splent_framework.utils.feature_utils import get_features_from_pyproject
@@ -22,11 +23,35 @@ from splent_framework.utils.pyproject_reader import PyprojectReader
22
23
 
23
24
 
24
25
  def _find_dependents(feature: str, product_dir: str) -> list[str]:
25
- """Find features that depend on `feature` via UVL constraints.
26
+ """Find the features that depend on `feature`, to roll back first.
26
27
 
27
- Uses FeatureLoadOrderResolver's UVL parsing (same logic as feature:order).
28
- Returns list of dependent feature package names.
28
+ The UVL constraints name them (``A => feature``, in constraint order, the
29
+ logic feature:order uses), and so does the refinement metadata the boot
30
+ reads: a refiner's migrations change its base's tables, so it is rolled
31
+ back before its base even when the UVL does not relate them. Returns
32
+ package names, the UVL dependents first and then any other refiner.
29
33
  """
34
+ dependents = _uvl_dependents(feature, product_dir)
35
+ for refiner in _refiners_of(feature, product_dir):
36
+ if refiner not in dependents:
37
+ dependents.append(refiner)
38
+ return dependents
39
+
40
+
41
+ def _refiners_of(feature: str, product_dir: str) -> list[str]:
42
+ """The product's features that refine `feature`, in declared order."""
43
+ try:
44
+ entries = PyprojectReader.for_product(product_dir).features_for_env(
45
+ os.getenv("SPLENT_ENV")
46
+ )
47
+ refines = read_refines(product_dir, entries)
48
+ except Exception:
49
+ return []
50
+ return [refiner for refiner, base in refines.items() if base == feature]
51
+
52
+
53
+ def _uvl_dependents(feature: str, product_dir: str) -> list[str]:
54
+ """Features whose UVL constraint says they require `feature`."""
30
55
  from splent_cli.services import spl_store
31
56
 
32
57
  try:
@@ -6,6 +6,7 @@ import click
6
6
  from splent_cli.utils.decorators import requires_db
7
7
  from splent_cli.services import context
8
8
  from splent_cli.utils.lifecycle import resolve_feature_key_from_entry
9
+ from splent_cli.utils.refinement_facts import read_refines
9
10
  from splent_cli.utils.feature_utils import (
10
11
  get_features_from_pyproject,
11
12
  normalize_namespace,
@@ -16,9 +17,11 @@ from splent_framework.utils.pyproject_reader import PyprojectReader
16
17
 
17
18
 
18
19
  def _resolve_feature_order(features_raw: list[str]) -> list[str]:
19
- """Return features in topological order using the product's UVL constraints.
20
+ """Return features in the order the product loads them.
20
21
 
21
- Falls back to the declared order in pyproject.toml if no UVL is available.
22
+ The UVL constraints order them, and every refiner comes after the
23
+ feature it refines (the refinement metadata the boot reads), with or
24
+ without a UVL. Features with neither keep their declared order.
22
25
  """
23
26
  splent_app = os.getenv("SPLENT_APP", "")
24
27
  working_dir = os.getenv("WORKING_DIR", "/workspace")
@@ -39,7 +42,8 @@ def _resolve_feature_order(features_raw: list[str]) -> list[str]:
39
42
  except (OSError, KeyError, AttributeError):
40
43
  pass
41
44
 
42
- return FeatureLoadOrderResolver().resolve(features_raw, uvl_path)
45
+ refines = read_refines(product_dir, features_raw) if product_dir else {}
46
+ return FeatureLoadOrderResolver().resolve(features_raw, uvl_path, refines=refines)
43
47
 
44
48
 
45
49
  def _collect_seeders(module_name):
@@ -1,5 +1,4 @@
1
1
  import os
2
- import tomli_w
3
2
  import click
4
3
  from splent_cli.services import context
5
4
  from splent_cli.utils.feature_utils import (
@@ -14,7 +13,8 @@ from splent_cli.utils.feature_utils import (
14
13
  remove_feature_link,
15
14
  write_features_to_data,
16
15
  )
17
- from splent_cli.utils.io_utils import load_toml, atomic_write
16
+ from splent_cli.utils.io_utils import load_toml
17
+ from splent_cli.utils.toml_edit import write_toml
18
18
  from splent_cli.utils.manifest import feature_key, remove_feature, set_feature_state
19
19
 
20
20
 
@@ -126,6 +126,15 @@ def feature_add(full_name, env_scope):
126
126
  click.echo(f" {short} already in {where}.")
127
127
  return
128
128
 
129
+ drop_feature_entries(data, feature_name, namespace=org_safe)
130
+ for key in target_keys:
131
+ features = read_feature_list(data, key)
132
+ features.append(full_name)
133
+ write_features_to_data(data, features, key=key)
134
+ # Written first: when the edit cannot be made exactly it is refused with
135
+ # nothing written, and the stale links and manifest entries are still there.
136
+ write_toml(pyproject_path, data)
137
+
129
138
  # Drop the stale declarations (other list, pinned version, other spelling of
130
139
  # the namespace) together with what each one created, so the feature ends up
131
140
  # declared once per list and with a single symlink.
@@ -137,13 +146,6 @@ def feature_add(full_name, env_scope):
137
146
  )
138
147
  click.echo(click.style(f" replacing {stale_entry} in {stale_key}", dim=True))
139
148
 
140
- drop_feature_entries(data, feature_name, namespace=org_safe)
141
- for key in target_keys:
142
- features = read_feature_list(data, key)
143
- features.append(full_name)
144
- write_features_to_data(data, features, key=key)
145
- atomic_write(pyproject_path, tomli_w.dumps(data))
146
-
147
149
  scope_label = f" ({env_scope} only)" if env_scope else ""
148
150
  click.echo(f" {short} added to {where}{scope_label}")
149
151
 
@@ -1,5 +1,4 @@
1
1
  import os
2
- import tomli_w
3
2
  import click
4
3
  from splent_cli.services import context, compose
5
4
  from splent_cli.utils.feature_utils import (
@@ -15,7 +14,8 @@ from splent_cli.utils.feature_utils import (
15
14
  set_feature_distribution,
16
15
  write_features_to_data,
17
16
  )
18
- from splent_cli.utils.io_utils import load_toml, atomic_write
17
+ from splent_cli.utils.io_utils import load_toml
18
+ from splent_cli.utils.toml_edit import write_toml
19
19
  from splent_cli.utils.manifest import feature_key, remove_feature, set_feature_state
20
20
 
21
21
 
@@ -156,7 +156,29 @@ def feature_attach(feature_identifier, version, env_scope):
156
156
  stale = [pair for pair in declared if pair not in wanted]
157
157
  where = ", ".join(target_keys)
158
158
 
159
- if sorted(declared) == sorted(wanted):
159
+ attaching = sorted(declared) != sorted(wanted)
160
+ if attaching:
161
+ drop_feature_entries(data, feature_name, namespace=namespace_fs)
162
+ for key in target_keys:
163
+ features = read_feature_list(data, key)
164
+ features.append(full_name)
165
+ write_features_to_data(data, features, key=key)
166
+
167
+ # ── PyPI name, when it is not the feature's own ──────────────────
168
+ # The production image installs pinned features from PyPI, so it has to
169
+ # know the name this version was published under. A release under a name
170
+ # of one's own (splent-feature-notes-alice) keeps the import package, and
171
+ # this pin, as splent_feature_notes.
172
+ distribution = read_project_name(versioned_dir)
173
+ renamed = set_feature_distribution(data, feature_name, distribution)
174
+
175
+ # One write, before anything else changes. When the edit cannot be made
176
+ # exactly it is refused with nothing written, and the old links and
177
+ # manifest entries are still there.
178
+ if attaching or renamed:
179
+ write_toml(pyproject_path, data)
180
+
181
+ if not attaching:
160
182
  click.echo(f" {short}@{version} already in {where}.")
161
183
  else:
162
184
  # Drop the stale declarations (other list, other version, other spelling
@@ -173,13 +195,6 @@ def feature_attach(feature_identifier, version, env_scope):
173
195
  click.echo(
174
196
  click.style(f" replacing {stale_entry} in {stale_key}", dim=True)
175
197
  )
176
-
177
- drop_feature_entries(data, feature_name, namespace=namespace_fs)
178
- for key in target_keys:
179
- features = read_feature_list(data, key)
180
- features.append(full_name)
181
- write_features_to_data(data, features, key=key)
182
- atomic_write(pyproject_path, tomli_w.dumps(data))
183
198
  if env_scope:
184
199
  scope_label = f" ({env_scope} only)"
185
200
  elif target_keys != ["features"]:
@@ -188,17 +203,8 @@ def feature_attach(feature_identifier, version, env_scope):
188
203
  scope_label = ""
189
204
  click.echo(f" {short}@{version} attached{scope_label}")
190
205
 
191
- # ── PyPI name, when it is not the feature's own ──────────────────
192
- # The production image installs pinned features from PyPI, so it has to
193
- # know the name this version was published under. A release under a name
194
- # of one's own (splent-feature-notes-alice) keeps the import package, and
195
- # this pin, as splent_feature_notes.
196
- distribution = read_project_name(versioned_dir)
197
- data = load_toml(pyproject_path, what="pyproject.toml")
198
- if set_feature_distribution(data, feature_name, distribution):
199
- atomic_write(pyproject_path, tomli_w.dumps(data))
200
- if distribution and distribution != feature_name:
201
- click.echo(f" pypi {short} is published on PyPI as {distribution}")
206
+ if renamed and distribution and distribution != feature_name:
207
+ click.echo(f" pypi {short} is published on PyPI as {distribution}")
202
208
 
203
209
  # ── Create/update symlink ─────────────────────────────────────────
204
210
  product_features_dir = os.path.join(product_path, "features", namespace_fs)