splent-cli 1.47.0__tar.gz → 1.48.1__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 (317) hide show
  1. {splent_cli-1.47.0/src/splent_cli.egg-info → splent_cli-1.48.1}/PKG-INFO +1 -1
  2. {splent_cli-1.47.0 → splent_cli-1.48.1}/pyproject.toml +1 -1
  3. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_deps.py +40 -0
  4. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_release.py +83 -2
  5. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_validate.py +15 -8
  6. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/frontend.py +148 -6
  7. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_.gitignore.j2 +3 -0
  8. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/Dockerfile.frontend +3 -1
  9. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/frontend/package-lock.json +830 -0
  10. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/frontend/package.json +2 -0
  11. splent_cli-1.48.1/src/splent_cli/toolchain/frontend/splent/lint.js +147 -0
  12. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/frontend/splent/plugin.js +108 -9
  13. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/frontend/splent/preset.js +41 -1
  14. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/frontend_check.py +158 -11
  15. {splent_cli-1.47.0 → splent_cli-1.48.1/src/splent_cli.egg-info}/PKG-INFO +1 -1
  16. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli.egg-info/SOURCES.txt +1 -0
  17. {splent_cli-1.47.0 → splent_cli-1.48.1}/COPYING +0 -0
  18. {splent_cli-1.47.0 → splent_cli-1.48.1}/COPYING.LESSER +0 -0
  19. {splent_cli-1.47.0 → splent_cli-1.48.1}/README.md +0 -0
  20. {splent_cli-1.47.0 → splent_cli-1.48.1}/setup.cfg +0 -0
  21. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/__init__.py +0 -0
  22. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/__main__.py +0 -0
  23. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/cli.py +0 -0
  24. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/__init__.py +0 -0
  25. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/__init__.py +0 -0
  26. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  27. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  28. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  29. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  30. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_size.py +0 -0
  31. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_status.py +0 -0
  32. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  33. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  34. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/__init__.py +0 -0
  35. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_contracts.py +0 -0
  36. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_docker.py +0 -0
  37. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_env.py +0 -0
  38. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_features.py +0 -0
  39. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_github.py +0 -0
  40. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_infra.py +0 -0
  41. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_product.py +0 -0
  42. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_pypi.py +0 -0
  43. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  44. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_releases.py +0 -0
  45. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/check/check_vendor.py +0 -0
  46. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/clear/__init__.py +0 -0
  47. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/clear/clear_build.py +0 -0
  48. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/clear/clear_log.py +0 -0
  49. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  50. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/command/__init__.py +0 -0
  51. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/command/command_create.py +0 -0
  52. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/coverage.py +0 -0
  53. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_console.py +0 -0
  54. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_dump.py +0 -0
  55. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_migrate.py +0 -0
  56. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_reset.py +0 -0
  57. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_restore.py +0 -0
  58. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_rollback.py +0 -0
  59. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_seed.py +0 -0
  60. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_status.py +0 -0
  61. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  62. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/doctor.py +0 -0
  63. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/env/env_list.py +0 -0
  64. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/env/env_set.py +0 -0
  65. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/env/env_show.py +0 -0
  66. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/export/__init__.py +0 -0
  67. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/export/export_puml.py +0 -0
  68. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_add.py +0 -0
  69. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  70. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  71. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  72. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  73. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_compile.py +0 -0
  74. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  75. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_create.py +0 -0
  76. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  77. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  78. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  79. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_drift.py +0 -0
  80. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_env.py +0 -0
  81. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  82. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_frontend_init.py +0 -0
  83. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_git.py +0 -0
  84. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  85. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  86. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  87. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  88. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_info.py +0 -0
  89. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  90. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_install.py +0 -0
  91. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_list.py +0 -0
  92. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_npm.py +0 -0
  93. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_order.py +0 -0
  94. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  95. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  96. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  97. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  98. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  99. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  100. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  101. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_search.py +0 -0
  102. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_status.py +0 -0
  103. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  104. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_test.py +0 -0
  105. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  106. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
  107. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  108. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  109. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  110. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/lint.py +0 -0
  111. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/locust.py +0 -0
  112. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/marketplace/login.py +0 -0
  113. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/marketplace/logout.py +0 -0
  114. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  115. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  116. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  117. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/__init__.py +0 -0
  118. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  119. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_build.py +0 -0
  120. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_clean.py +0 -0
  121. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_commands.py +0 -0
  122. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_config.py +0 -0
  123. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_configure.py +0 -0
  124. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_console.py +0 -0
  125. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_containers.py +0 -0
  126. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_create.py +0 -0
  127. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_deploy.py +0 -0
  128. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_derive.py +0 -0
  129. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_deselect.py +0 -0
  130. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_down.py +0 -0
  131. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_drift.py +0 -0
  132. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_env.py +0 -0
  133. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_frontend_audit.py +0 -0
  134. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_list.py +0 -0
  135. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_logs.py +0 -0
  136. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_missing.py +0 -0
  137. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_params.py +0 -0
  138. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_port.py +0 -0
  139. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_release.py +0 -0
  140. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_resolve.py +0 -0
  141. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_restart.py +0 -0
  142. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_routes.py +0 -0
  143. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_run.py +0 -0
  144. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_select.py +0 -0
  145. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_shell.py +0 -0
  146. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_signals.py +0 -0
  147. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  148. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_test.py +0 -0
  149. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/product/product_up.py +0 -0
  150. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/release/__init__.py +0 -0
  151. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/release/release_core.py +0 -0
  152. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/release/release_resume.py +0 -0
  153. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/selenium.py +0 -0
  154. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/__init__.py +0 -0
  155. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  156. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  157. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  158. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  159. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_create.py +0 -0
  160. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  161. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_features.py +0 -0
  162. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  163. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_info.py +0 -0
  164. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_list.py +0 -0
  165. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  166. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  167. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  168. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  169. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  170. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/tokens_setup.py +0 -0
  171. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  172. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/commands/version.py +0 -0
  173. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/__init__.py +0 -0
  174. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/compose.py +0 -0
  175. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/context.py +0 -0
  176. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/credentials.py +0 -0
  177. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/deploy_bundle.py +0 -0
  178. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/editable_refresh.py +0 -0
  179. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/image_registry.py +0 -0
  180. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/index_refresh.py +0 -0
  181. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/marketplace.py +0 -0
  182. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/marketplace_api.py +0 -0
  183. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/marketplace_url.py +0 -0
  184. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/neutral_app.py +0 -0
  185. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/own_commands.py +0 -0
  186. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/preflight.py +0 -0
  187. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/pypi_quota.py +0 -0
  188. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/registry.py +0 -0
  189. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/release.py +0 -0
  190. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/release_dry_run.py +0 -0
  191. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/release_gate.py +0 -0
  192. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/spl_store.py +0 -0
  193. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/services/web_container.py +0 -0
  194. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
  195. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
  196. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
  197. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
  198. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
  199. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
  200. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
  201. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
  202. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_frontend.npmrc.j2 +0 -0
  203. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_frontend_scripts.js.j2 +0 -0
  204. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
  205. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
  206. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
  207. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
  208. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
  209. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
  210. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
  211. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
  212. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
  213. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
  214. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
  215. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
  216. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
  217. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
  218. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
  219. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
  220. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
  221. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
  222. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
  223. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
  224. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
  225. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
  226. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
  227. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
  228. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
  229. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
  230. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/licenses/COPYING +0 -0
  231. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
  232. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
  233. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
  234. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
  235. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
  236. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
  237. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
  238. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
  239. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
  240. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
  241. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
  242. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
  243. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
  244. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
  245. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_400.html +0 -0
  246. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_401.html +0 -0
  247. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_404.html +0 -0
  248. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_500.html +0 -0
  249. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
  250. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
  251. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
  252. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
  253. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
  254. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_app.css +0 -0
  255. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_app.js +0 -0
  256. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_base_template.html +0 -0
  257. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
  258. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +0 -0
  259. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
  260. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
  261. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
  262. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_dropzone.css +0 -0
  263. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
  264. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
  265. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
  266. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_favicon.svg +0 -0
  267. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
  268. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
  269. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_own.css +0 -0
  270. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
  271. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
  272. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
  273. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/frontend/splent/build.js +0 -0
  274. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/frontend/splent/legacy.js +0 -0
  275. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/toolchain/frontend/splent/singletons.json +0 -0
  276. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/__init__.py +0 -0
  277. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/archetype.py +0 -0
  278. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/cache_utils.py +0 -0
  279. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/ci_services.py +0 -0
  280. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/ci_workflow.py +0 -0
  281. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/command_loader.py +0 -0
  282. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/contract_freshness.py +0 -0
  283. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/db_client.py +0 -0
  284. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/db_utils.py +0 -0
  285. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/decorators.py +0 -0
  286. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/deploy_freshness.py +0 -0
  287. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/dynamic_imports.py +0 -0
  288. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/feature_installer.py +0 -0
  289. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/feature_naming.py +0 -0
  290. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/feature_params.py +0 -0
  291. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/feature_requirements.py +0 -0
  292. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/feature_resolution.py +0 -0
  293. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/feature_utils.py +0 -0
  294. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/forge.py +0 -0
  295. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/frontend_licenses.py +0 -0
  296. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/git_url.py +0 -0
  297. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/integrity.py +0 -0
  298. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/io_utils.py +0 -0
  299. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/lifecycle.py +0 -0
  300. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/manifest.py +0 -0
  301. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/menu_keys.py +0 -0
  302. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/migration_lock.py +0 -0
  303. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/npm_semver.py +0 -0
  304. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/path_utils.py +0 -0
  305. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/proc.py +0 -0
  306. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/redact.py +0 -0
  307. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/refinement_facts.py +0 -0
  308. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/running_version.py +0 -0
  309. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/template_drift.py +0 -0
  310. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/toml_edit.py +0 -0
  311. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/upgrade_plan.py +0 -0
  312. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/uvl_diagnosis.py +0 -0
  313. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli/utils/uvl_params.py +0 -0
  314. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  315. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli.egg-info/entry_points.txt +0 -0
  316. {splent_cli-1.47.0 → splent_cli-1.48.1}/src/splent_cli.egg-info/requires.txt +0 -0
  317. {splent_cli-1.47.0 → splent_cli-1.48.1}/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.47.0
3
+ Version: 1.48.1
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.47.0"
7
+ version = "1.48.1"
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"
@@ -152,6 +152,34 @@ def _scan_feature_imports(
152
152
  return python_imports, template_deps
153
153
 
154
154
 
155
+ def _optional_features(feature_path: str) -> set[str]:
156
+ """The packages a feature's contract names in requires.features_optional.
157
+
158
+ A feature reaches those only when the product has them (a tolerant
159
+ import, a has_endpoint guard), so its reference to one needs no UVL
160
+ constraint: the model leaves the other feature optional, and nothing
161
+ breaks without it. Version ranges ("hubfile>=0.5.0") are dropped.
162
+ """
163
+ import tomllib
164
+
165
+ try:
166
+ with open(os.path.join(feature_path, "pyproject.toml"), "rb") as fh:
167
+ data = tomllib.load(fh)
168
+ except (OSError, tomllib.TOMLDecodeError):
169
+ return set()
170
+ requires = (
171
+ data.get("tool", {}).get("splent", {}).get("contract", {}).get("requires", {})
172
+ )
173
+ names = set()
174
+ for entry in requires.get("features_optional") or []:
175
+ name = re.split(r"[<>=!~\s]", str(entry), maxsplit=1)[0]
176
+ if name:
177
+ names.add(
178
+ name if name.startswith("splent_feature_") else f"splent_feature_{name}"
179
+ )
180
+ return names
181
+
182
+
155
183
  # ---------------------------------------------------------------------------
156
184
  # Feature path resolver
157
185
  # ---------------------------------------------------------------------------
@@ -271,6 +299,7 @@ def check_deps():
271
299
 
272
300
  py_imports, tpl_deps = _scan_feature_imports(fpath, pkg_name, all_packages)
273
301
  allowed = allowed_deps.get(pkg_name, set())
302
+ optional = _optional_features(fpath)
274
303
  all_deps = py_imports | tpl_deps
275
304
 
276
305
  if not all_deps:
@@ -296,6 +325,17 @@ def check_deps():
296
325
  )
297
326
  )
298
327
  ok += 1
328
+ elif imp in optional:
329
+ click.echo(
330
+ click.style(" [✔] ", fg="green")
331
+ + click.style(f"{short}", bold=True)
332
+ + f" {source} {imp_short}"
333
+ + click.style(
334
+ f" (optional: {short}'s contract lists it in features_optional)",
335
+ fg="bright_black",
336
+ )
337
+ )
338
+ ok += 1
299
339
  else:
300
340
  reverse_allowed = allowed_deps.get(imp, set())
301
341
  if pkg_name in reverse_allowed:
@@ -237,6 +237,68 @@ def _extract_templates(src_dir: Path) -> list[str]:
237
237
 
238
238
 
239
239
  _SLOT_CALL = re.compile(r"""(?:get|render)_template_hooks\s*\(\s*['"]([^'"]+)['"]""")
240
+ _SLOT_FUNCTIONS = ("get_template_hooks", "render_template_hooks")
241
+ _MACRO = re.compile(
242
+ r"\{%-?\s*macro\s+(\w+)\s*\(\s*(\w+)(.*?)\{%-?\s*endmacro", re.DOTALL
243
+ )
244
+
245
+
246
+ def _python_slot_helpers(source: str) -> set[str]:
247
+ """The functions of SOURCE that open the slot their first argument
248
+ names: they hand it to get_template_hooks or render_template_hooks."""
249
+ try:
250
+ tree = ast.parse(source)
251
+ except SyntaxError:
252
+ return set()
253
+ helpers = set()
254
+ for node in ast.walk(tree):
255
+ if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
256
+ continue
257
+ params = [arg.arg for arg in node.args.posonlyargs + node.args.args]
258
+ if params[:1] in (["self"], ["cls"]):
259
+ params = params[1:]
260
+ if not params:
261
+ continue
262
+ for call in ast.walk(node):
263
+ if not isinstance(call, ast.Call) or not call.args:
264
+ continue
265
+ called = call.func
266
+ if isinstance(called, ast.Attribute):
267
+ name = called.attr
268
+ elif isinstance(called, ast.Name):
269
+ name = called.id
270
+ else:
271
+ name = None
272
+ first = call.args[0]
273
+ if (
274
+ name in _SLOT_FUNCTIONS
275
+ and isinstance(first, ast.Name)
276
+ and first.id == params[0]
277
+ ):
278
+ helpers.add(node.name)
279
+ break
280
+ return helpers
281
+
282
+
283
+ def _template_slot_helpers(source: str) -> set[str]:
284
+ """The macros of SOURCE that open the slot their first argument names."""
285
+ helpers = set()
286
+ for name, param, body in _MACRO.findall(source):
287
+ opens = re.compile(
288
+ r"(?:get|render)_template_hooks\s*\(\s*" + re.escape(param) + r"\s*[,)]"
289
+ )
290
+ if opens.search(body):
291
+ helpers.add(name)
292
+ return helpers
293
+
294
+
295
+ def _helper_calls(helpers: set[str]) -> re.Pattern | None:
296
+ """A call of one of HELPERS with a slot name as its first argument, as
297
+ a template writes it: ``hub.slot_of("x.y", …)`` or ``slot_of('x.y')``."""
298
+ if not helpers:
299
+ return None
300
+ names = "|".join(re.escape(name) for name in sorted(helpers))
301
+ return re.compile(r"""\b(?:""" + names + r""")\s*\(\s*['"]([^'"]+)['"]""")
240
302
 
241
303
 
242
304
  def _extract_template_hook_slots(src_dir: Path) -> list[str]:
@@ -250,19 +312,38 @@ def _extract_template_hook_slots(src_dir: Path) -> list[str]:
250
312
  that slot is as much the feature's: a scan of the templates alone left
251
313
  it out of ``extensible.hooks``. Tests are not the feature's code, and a
252
314
  layout.* slot the code reads is the product's.
315
+
316
+ A helper may also take the slot's name from its caller: a function of
317
+ the feature's Python, or a macro of its templates, that hands its first
318
+ argument to one of the two. Then the slot is the literal a template
319
+ (or the code) passes it, ``shell.slot_of("layout.sidebar", …)``.
253
320
  """
254
321
  if not src_dir.exists():
255
322
  return []
256
323
  slots: set[str] = set()
257
324
  templates_dir = src_dir / "templates"
325
+ templates = {}
258
326
  if templates_dir.exists():
259
327
  for html_file in templates_dir.rglob("*.html"):
260
- slots.update(_SLOT_CALL.findall(html_file.read_text()))
328
+ templates[html_file] = html_file.read_text()
329
+ sources = {}
261
330
  for py_file in _feature_sources(src_dir):
262
331
  try:
263
- found = _SLOT_CALL.findall(py_file.read_text())
332
+ sources[py_file] = py_file.read_text()
264
333
  except (OSError, UnicodeDecodeError):
265
334
  continue
335
+ helpers = set()
336
+ for text in sources.values():
337
+ helpers |= _python_slot_helpers(text)
338
+ for text in templates.values():
339
+ helpers |= _template_slot_helpers(text)
340
+ calls = _helper_calls(helpers)
341
+ for text in templates.values():
342
+ slots.update(_SLOT_CALL.findall(text))
343
+ if calls:
344
+ slots.update(calls.findall(text))
345
+ for text in sources.values():
346
+ found = _SLOT_CALL.findall(text) + (calls.findall(text) if calls else [])
266
347
  slots.update(s for s in found if not s.startswith(_PRODUCT_SLOT_PREFIX))
267
348
  return sorted(slots)
268
349
 
@@ -19,8 +19,9 @@ product:validate checks a product in seven phases.
19
19
  7. Frontend (bundles). Does the shell provide, at a version in range,
20
20
  what each feature with frontend/ takes from the page? Does each bundle
21
21
  match its manifest, ship what its licence allows and no singleton
22
- another one ships? What legacy features use, and what anything loads
23
- from other origins, is said (splent_cli.utils.frontend_check).
22
+ another one ships, and load nothing from another origin? Does the
23
+ product approve what each one asks to add to the CSP? What legacy
24
+ features use and load is said (splent_cli.utils.frontend_check).
24
25
  """
25
26
 
26
27
  import os
@@ -206,6 +207,7 @@ def _run_deps_check(workspace, product, data=None, uvl_path=None):
206
207
  a model to check against instead (--model).
207
208
  """
208
209
  from splent_cli.commands.check.check_deps import (
210
+ _optional_features,
209
211
  _parse_uvl_deps,
210
212
  _resolve_feature_paths,
211
213
  _scan_feature_imports,
@@ -258,6 +260,8 @@ def _run_deps_check(workspace, product, data=None, uvl_path=None):
258
260
 
259
261
  py_imports, tpl_deps = _scan_feature_imports(fpath, pkg_name, all_packages)
260
262
  allowed = allowed_deps.get(pkg_name, set())
263
+ # What the feature reaches only when the product has it.
264
+ optional = _optional_features(fpath)
261
265
  all_deps = py_imports | tpl_deps
262
266
 
263
267
  if not all_deps:
@@ -266,7 +270,7 @@ def _run_deps_check(workspace, product, data=None, uvl_path=None):
266
270
 
267
271
  for imp in sorted(all_deps):
268
272
  imp_short = pkg_to_short.get(imp, imp)
269
- if imp in allowed:
273
+ if imp in allowed or imp in optional:
270
274
  ok_count += 1
271
275
  else:
272
276
  reverse_allowed = allowed_deps.get(imp, set())
@@ -610,7 +614,8 @@ def _run_frontend_check(workspace, product_dir, data, env=None):
610
614
  from splent_cli.utils.frontend_check import check_product
611
615
 
612
616
  entries = read_features_from_data(data, env or os.getenv("SPLENT_ENV", "dev"))
613
- return check_product(workspace, product_dir, entries)
617
+ approvals = ((data.get("tool") or {}).get("splent") or {}).get("csp")
618
+ return check_product(workspace, product_dir, entries, csp_approvals=approvals)
614
619
 
615
620
 
616
621
  def _report_frontend(check) -> bool:
@@ -619,10 +624,12 @@ def _report_frontend(check) -> bool:
619
624
  What the shell provides comes first, and where it was read. A peer or
620
625
  capability nothing provides, or at a version out of its range, a
621
626
  singleton two bundles ship, a licence the feature cannot ship, a
622
- release whose bundle is not the one it built and a global a template
623
- takes without declaring it are errors. A legacy feature, a working copy
624
- built before its sources changed and anything loaded from another
625
- origin are warnings.
627
+ release whose bundle is not the one it built, a global a template
628
+ takes without declaring it, a feature with frontend/ that loads from
629
+ another origin and a CSP source the policy never takes are errors. A
630
+ legacy feature, a working copy built before its sources changed, what
631
+ a legacy feature or the product loads from another origin and a CSP
632
+ request the product does not approve are warnings.
626
633
  """
627
634
  from splent_cli.utils.frontend_check import FRONTEND
628
635
 
@@ -12,8 +12,11 @@ legacy: its ``assets/js/webpack.config.js`` builds against the product's
12
12
  node_modules in the product's web container, exactly as before, until CLI
13
13
  2.0.
14
14
 
15
- A build writes ``assets/dist`` and ``assets/meta/manifest.json``, the files
16
- it wrote and the packages it put in them. A production build adds
15
+ A build lints the feature's own code first, with eslint's ``no-undef``
16
+ against the globals of the page it declares (toolchain/frontend/splent/
17
+ lint.js), and fails on a name it never declared. It writes ``assets/dist``
18
+ and ``assets/meta/manifest.json``, the files it wrote, the packages it put
19
+ in them and the page globals its code reads. A production build adds
17
20
  ``assets/meta/third_party.json``, their licence texts, and a release
18
21
  (:func:`release_build`) adds ``assets/meta/sbom.cdx.json`` and gates on
19
22
  the licences (utils/frontend_licenses.py) and on ``npm audit``.
@@ -31,9 +34,12 @@ import datetime
31
34
  import hashlib
32
35
  import json
33
36
  import os
37
+ import re
34
38
  import shutil
35
39
  import socket
36
40
  import subprocess
41
+ import sys
42
+ import tempfile
37
43
  import uuid
38
44
  from pathlib import Path
39
45
 
@@ -408,14 +414,20 @@ def npm(
408
414
 
409
415
 
410
416
  def install(feature_root, *, image=None, force=False, echo=click.echo) -> None:
411
- """Install frontend/node_modules from the lock, unless it already is.
417
+ """Install what the build takes from the network: frontend/node_modules
418
+ from the lock, and the files splent.vendored fetches
419
+ (:func:`fetch_vendored`), each unless it already is. FORCE installs
420
+ anyway, as a release does."""
421
+ _install_node_modules(feature_root, image=image, force=force, echo=echo)
422
+ fetch_vendored(feature_root, force=force, echo=echo)
412
423
 
413
- ``npm ci --omit=peer --ignore-scripts``: exactly the lock, no peer (the
424
+
425
+ def _install_node_modules(feature_root, *, image, force, echo) -> None:
426
+ """``npm ci --omit=peer --ignore-scripts``: exactly the lock, no peer (the
414
427
  shell provides those, so a peer the bundle forgot to declare fails as
415
428
  "Module not found" instead of shipping a second copy) and no install
416
429
  script. The sha256 of the lock it installed is sealed into node_modules,
417
- so the next build installs only when the lock changed. FORCE installs
418
- anyway, as a release does.
430
+ so the next build installs only when the lock changed.
419
431
  """
420
432
  frontend = Path(feature_root) / FRONTEND_DIR
421
433
  lock = frontend / LOCK
@@ -452,6 +464,136 @@ def install(feature_root, *, image=None, force=False, echo=click.echo) -> None:
452
464
  _give_back(seal)
453
465
 
454
466
 
467
+ # ── Vendored files ────────────────────────────────────────────────────
468
+
469
+ # What a feature serves that is not npm's (the Python wheels an in-browser
470
+ # runtime installs), as splent.vendored declares it: {name, version,
471
+ # license, source, path, sha256}, ``path`` being where it is served, below
472
+ # assets/. One served from assets/dist is copied there by the build, from
473
+ # the feature's repository when it names ``from`` (a file of frontend/,
474
+ # such as a wheel the feature builds itself), or else from what this step
475
+ # fetched into frontend/vendored/, which no repository keeps.
476
+ VENDORED_DIR = "vendored"
477
+
478
+ # name-version(-build)?-python-abi-platform.whl
479
+ _WHEEL = re.compile(
480
+ r"^(?P<name>[^-]+)-(?P<version>[^-]+)(?:-\d[^-]*)?"
481
+ r"-(?P<python>[^-]+)-(?P<abi>[^-]+)-(?P<platform>[^-]+)\.whl$"
482
+ )
483
+
484
+
485
+ def vendored(pkg: dict) -> list[dict]:
486
+ """splent.vendored of PKG, the entries it declares as dicts."""
487
+ entries = (pkg.get("splent") or {}).get("vendored") or []
488
+ return [entry for entry in entries if isinstance(entry, dict)]
489
+
490
+
491
+ def _pip_download_args(entry: dict, filename: str, dest: Path) -> list[str]:
492
+ """``pip download`` of exactly FILENAME: a wheel, from PyPI when the
493
+ entry's source is ``pypi``, or from its https URL, for the platform its
494
+ tags name (``any`` for a pure one, ``pyodide_2024_0_wasm32`` for one
495
+ built for Pyodide)."""
496
+ name = entry.get("name") or "?"
497
+ tags = _WHEEL.match(filename)
498
+ if tags is None:
499
+ raise FrontendError(
500
+ f"splent.vendored {name}: {filename} is no wheel, and only wheels are "
501
+ "fetched. Keep the file in frontend/ and name it in from."
502
+ )
503
+ source = str(entry.get("source") or "pypi")
504
+ if source == "pypi":
505
+ requirement = f"{entry.get('name')}=={entry.get('version')}"
506
+ elif source.startswith("https://"):
507
+ requirement = source
508
+ else:
509
+ raise FrontendError(
510
+ f"splent.vendored {name}: source is pypi or the https URL of the "
511
+ f"wheel, not {source!r}; a file the feature keeps names it in from."
512
+ )
513
+ args = [
514
+ sys.executable,
515
+ "-m",
516
+ "pip",
517
+ "download",
518
+ "--no-deps",
519
+ "--quiet",
520
+ "--disable-pip-version-check",
521
+ "--only-binary",
522
+ ":all:",
523
+ "--dest",
524
+ str(dest),
525
+ "--platform",
526
+ tags["platform"],
527
+ ]
528
+ python = tags["python"].split(".")[0]
529
+ if python.startswith("cp") and python[2:].isdigit():
530
+ version = f"{python[2]}.{python[3:]}"
531
+ args += ["--implementation", "cp", "--python-version", version]
532
+ args += ["--abi", tags["abi"].split(".")[0]]
533
+ return [*args, requirement]
534
+
535
+
536
+ def fetch_vendored(feature_root, *, force=False, echo=click.echo) -> None:
537
+ """Fetch the wheels splent.vendored serves from assets/dist into
538
+ frontend/vendored/, with ``pip download``, and check each against the
539
+ sha256 the entry declares; and check the files the feature keeps for
540
+ it (``from``). A file already there with its sha256 is not fetched
541
+ again, unless FORCE, as a release does."""
542
+ if not declares(feature_root):
543
+ return
544
+ entries = vendored(package(feature_root))
545
+ frontend = Path(feature_root) / FRONTEND_DIR
546
+ target = frontend / VENDORED_DIR
547
+ for entry in entries:
548
+ name = entry.get("name") or "?"
549
+ expected = entry.get("sha256")
550
+ if not str(entry.get("path") or "").startswith("dist/"):
551
+ continue
552
+ if not expected:
553
+ raise FrontendError(f"splent.vendored {name} needs a sha256")
554
+ if entry.get("from"):
555
+ kept = (frontend / str(entry["from"])).resolve()
556
+ if not kept.is_relative_to(frontend.resolve()) or not kept.is_file():
557
+ raise FrontendError(
558
+ f"splent.vendored {name}: from names {entry['from']}, which is "
559
+ "not a file in frontend/"
560
+ )
561
+ if _sha256(kept) != expected:
562
+ raise FrontendError(
563
+ f"splent.vendored {name}: frontend/{entry['from']} is not the "
564
+ "file its sha256 names"
565
+ )
566
+ continue
567
+ filename = Path(str(entry["path"])).name
568
+ path = target / filename
569
+ if not force and path.is_file() and _sha256(path) == expected:
570
+ continue
571
+ echo(f" pip fetching {filename} (splent.vendored)...")
572
+ target.mkdir(parents=True, exist_ok=True)
573
+ with tempfile.TemporaryDirectory(dir=target) as scratch:
574
+ args = _pip_download_args(entry, filename, Path(scratch))
575
+ result = subprocess.run(
576
+ args, capture_output=True, text=True, timeout=INSTALL_TIMEOUT
577
+ )
578
+ got = Path(scratch) / filename
579
+ if result.returncode != 0 or not got.is_file():
580
+ said = _head(result.stderr or result.stdout, 4) or " (no output)"
581
+ found = ", ".join(sorted(p.name for p in Path(scratch).iterdir()))
582
+ raise FrontendError(
583
+ f"pip download did not fetch {filename}"
584
+ + (f" (it fetched {found})" if found else "")
585
+ + f":\n{said}"
586
+ )
587
+ if _sha256(got) != expected:
588
+ raise FrontendError(
589
+ f"splent.vendored {name}: the {filename} pip fetched has another "
590
+ "sha256 than the one the entry declares"
591
+ )
592
+ got.replace(path)
593
+ _give_back(target)
594
+ _give_back(path)
595
+
596
+
455
597
  def _give_back(path: Path) -> None:
456
598
  """A file this process wrote as root goes to the workspace's owner."""
457
599
  if os.geteuid() != 0:
@@ -26,6 +26,9 @@ app.log.*
26
26
  /dist/
27
27
  src/*/*/assets/dist/
28
28
  src/*/*/assets/meta/
29
+ # The wheels splent.vendored names, which feature:compile and the release
30
+ # fetch into frontend/vendored/ and check against their sha256.
31
+ frontend/vendored/
29
32
  {% else %}# Build artifacts: the package build's, at the root, and the bundles and
30
33
  # source maps webpack writes to assets/dist. Only those: the dist/ of a
31
34
  # vendored npm package is its source here, webpack resolves the package
@@ -2,7 +2,9 @@
2
2
  #
3
3
  # A feature declares its JavaScript dependencies in frontend/package.json,
4
4
  # and this image brings everything else: Node, npm, webpack, the loaders and
5
- # plugins, the SPLENT preset and the registry of singletons. webpack is no
5
+ # plugins, eslint (every build lints the feature's own code for globals of
6
+ # the page it never declared), the SPLENT preset and the registry of
7
+ # singletons. webpack is no
6
8
  # devDependency of any feature, so a feature's node_modules holds what its
7
9
  # bundle ships and nothing more, and every feature builds with the same
8
10
  # toolchain, the way a Python package builds in an isolated [build-system].