splent-cli 1.45.0__tar.gz → 1.47.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 (316) hide show
  1. {splent_cli-1.45.0/src/splent_cli.egg-info → splent_cli-1.47.0}/PKG-INFO +1 -1
  2. {splent_cli-1.45.0 → splent_cli-1.47.0}/pyproject.toml +4 -2
  3. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_compile.py +296 -11
  4. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_create.py +58 -4
  5. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_drift.py +6 -4
  6. splent_cli-1.47.0/src/splent_cli/commands/feature/feature_frontend_init.py +311 -0
  7. splent_cli-1.47.0/src/splent_cli/commands/feature/feature_npm.py +80 -0
  8. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_release.py +149 -23
  9. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_sync_template.py +7 -4
  10. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_unlock.py +22 -1
  11. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_build.py +36 -1
  12. splent_cli-1.47.0/src/splent_cli/commands/product/product_frontend_audit.py +323 -0
  13. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_release.py +2 -0
  14. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_validate.py +104 -7
  15. splent_cli-1.47.0/src/splent_cli/services/frontend.py +925 -0
  16. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/preflight.py +30 -0
  17. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/release_dry_run.py +120 -5
  18. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +12 -3
  19. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +15 -3
  20. splent_cli-1.47.0/src/splent_cli/templates/feature/feature_frontend.npmrc.j2 +9 -0
  21. splent_cli-1.47.0/src/splent_cli/templates/feature/feature_frontend_scripts.js.j2 +15 -0
  22. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +21 -12
  23. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_base_template.html +7 -0
  24. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +21 -4
  25. splent_cli-1.47.0/src/splent_cli/toolchain/Dockerfile.frontend +46 -0
  26. splent_cli-1.47.0/src/splent_cli/toolchain/frontend/package-lock.json +1300 -0
  27. splent_cli-1.47.0/src/splent_cli/toolchain/frontend/package.json +14 -0
  28. splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/build.js +76 -0
  29. splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/legacy.js +52 -0
  30. splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/plugin.js +288 -0
  31. splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/preset.js +208 -0
  32. splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/singletons.json +18 -0
  33. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/deploy_freshness.py +8 -3
  34. splent_cli-1.47.0/src/splent_cli/utils/frontend_check.py +866 -0
  35. splent_cli-1.47.0/src/splent_cli/utils/frontend_licenses.py +298 -0
  36. splent_cli-1.47.0/src/splent_cli/utils/npm_semver.py +200 -0
  37. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/template_drift.py +24 -1
  38. {splent_cli-1.45.0 → splent_cli-1.47.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
  39. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/SOURCES.txt +17 -0
  40. {splent_cli-1.45.0 → splent_cli-1.47.0}/COPYING +0 -0
  41. {splent_cli-1.45.0 → splent_cli-1.47.0}/COPYING.LESSER +0 -0
  42. {splent_cli-1.45.0 → splent_cli-1.47.0}/README.md +0 -0
  43. {splent_cli-1.45.0 → splent_cli-1.47.0}/setup.cfg +0 -0
  44. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/__init__.py +0 -0
  45. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/__main__.py +0 -0
  46. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/cli.py +0 -0
  47. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/__init__.py +0 -0
  48. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  49. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  50. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  51. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  52. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  53. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  54. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  55. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  56. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  57. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/__init__.py +0 -0
  58. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
  59. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  60. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  61. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_env.py +0 -0
  62. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_features.py +0 -0
  63. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_github.py +0 -0
  64. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_infra.py +0 -0
  65. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_product.py +0 -0
  66. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
  67. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  68. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_releases.py +0 -0
  69. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
  70. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/__init__.py +0 -0
  71. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
  72. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
  73. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
  74. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/command/__init__.py +0 -0
  75. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/command/command_create.py +0 -0
  76. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/coverage.py +0 -0
  77. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_console.py +0 -0
  78. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_dump.py +0 -0
  79. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
  80. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_reset.py +0 -0
  81. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  82. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  83. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  84. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_status.py +0 -0
  85. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
  86. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/doctor.py +0 -0
  87. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/env/env_list.py +0 -0
  88. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/env/env_set.py +0 -0
  89. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/env/env_show.py +0 -0
  90. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/export/__init__.py +0 -0
  91. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/export/export_puml.py +0 -0
  92. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
  93. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
  94. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
  95. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  96. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
  97. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
  98. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  99. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
  100. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  101. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
  102. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  103. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  104. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  105. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  106. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  107. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
  108. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
  109. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
  110. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
  111. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
  112. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  113. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
  114. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
  115. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  116. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  117. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
  118. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
  119. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
  120. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  121. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
  122. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
  123. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  124. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
  125. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  126. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
  127. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/lint.py +0 -0
  128. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/locust.py +0 -0
  129. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/login.py +0 -0
  130. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
  131. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
  132. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
  133. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
  134. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/__init__.py +0 -0
  135. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
  136. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_clean.py +0 -0
  137. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  138. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_config.py +0 -0
  139. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_configure.py +0 -0
  140. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_console.py +0 -0
  141. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_containers.py +0 -0
  142. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_create.py +0 -0
  143. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
  144. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_derive.py +0 -0
  145. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  146. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_down.py +0 -0
  147. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  148. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_env.py +0 -0
  149. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_list.py +0 -0
  150. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  151. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_missing.py +0 -0
  152. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_params.py +0 -0
  153. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_port.py +0 -0
  154. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
  155. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_restart.py +0 -0
  156. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  157. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_run.py +0 -0
  158. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_select.py +0 -0
  159. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  160. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  161. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  162. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_test.py +0 -0
  163. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_up.py +0 -0
  164. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/release/__init__.py +0 -0
  165. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/release/release_core.py +0 -0
  166. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/release/release_resume.py +0 -0
  167. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/selenium.py +0 -0
  168. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/__init__.py +0 -0
  169. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
  170. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  171. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
  172. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
  173. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  174. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  175. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
  176. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  177. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  178. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  179. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
  180. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
  181. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
  182. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
  183. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  184. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/tokens_setup.py +0 -0
  185. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  186. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/commands/version.py +0 -0
  187. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/__init__.py +0 -0
  188. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/compose.py +0 -0
  189. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/context.py +0 -0
  190. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/credentials.py +0 -0
  191. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/deploy_bundle.py +0 -0
  192. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/editable_refresh.py +0 -0
  193. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/image_registry.py +0 -0
  194. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/index_refresh.py +0 -0
  195. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/marketplace.py +0 -0
  196. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/marketplace_api.py +0 -0
  197. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/marketplace_url.py +0 -0
  198. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/neutral_app.py +0 -0
  199. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/own_commands.py +0 -0
  200. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/pypi_quota.py +0 -0
  201. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/registry.py +0 -0
  202. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/release.py +0 -0
  203. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/release_gate.py +0 -0
  204. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/spl_store.py +0 -0
  205. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/services/web_container.py +0 -0
  206. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
  207. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
  208. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
  209. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
  210. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
  211. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
  212. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
  213. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
  214. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
  215. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
  216. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
  217. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
  218. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
  219. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
  220. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
  221. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
  222. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
  223. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
  224. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
  225. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
  226. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
  227. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
  228. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
  229. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
  230. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
  231. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
  232. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
  233. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
  234. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
  235. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
  236. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
  237. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
  238. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
  239. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/licenses/COPYING +0 -0
  240. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
  241. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
  242. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
  243. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
  244. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
  245. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
  246. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
  247. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
  248. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
  249. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
  250. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
  251. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
  252. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
  253. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
  254. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_400.html +0 -0
  255. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_401.html +0 -0
  256. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_404.html +0 -0
  257. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_500.html +0 -0
  258. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
  259. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
  260. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
  261. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
  262. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_app.css +0 -0
  263. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_app.js +0 -0
  264. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
  265. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
  266. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
  267. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
  268. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
  269. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
  270. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
  271. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
  272. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
  273. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
  274. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
  275. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_own.css +0 -0
  276. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
  277. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
  278. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
  279. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/__init__.py +0 -0
  280. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/archetype.py +0 -0
  281. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/cache_utils.py +0 -0
  282. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/ci_services.py +0 -0
  283. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/ci_workflow.py +0 -0
  284. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/command_loader.py +0 -0
  285. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  286. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/db_client.py +0 -0
  287. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/db_utils.py +0 -0
  288. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/decorators.py +0 -0
  289. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  290. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_installer.py +0 -0
  291. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_naming.py +0 -0
  292. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_params.py +0 -0
  293. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_requirements.py +0 -0
  294. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_resolution.py +0 -0
  295. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_utils.py +0 -0
  296. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/forge.py +0 -0
  297. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/git_url.py +0 -0
  298. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/integrity.py +0 -0
  299. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/io_utils.py +0 -0
  300. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/lifecycle.py +0 -0
  301. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/manifest.py +0 -0
  302. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/menu_keys.py +0 -0
  303. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/migration_lock.py +0 -0
  304. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/path_utils.py +0 -0
  305. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/proc.py +0 -0
  306. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/redact.py +0 -0
  307. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/refinement_facts.py +0 -0
  308. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/running_version.py +0 -0
  309. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/toml_edit.py +0 -0
  310. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
  311. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/uvl_diagnosis.py +0 -0
  312. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli/utils/uvl_params.py +0 -0
  313. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  314. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  315. {splent_cli-1.45.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/requires.txt +0 -0
  316. {splent_cli-1.45.0 → splent_cli-1.47.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.45.0
3
+ Version: 1.47.0
4
4
  Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
5
  Author-email: DiversoLab <diversolab@us.es>
6
6
  License-Expression: LGPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.45.0"
7
+ version = "1.47.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"
@@ -78,8 +78,10 @@ where = ["src"]
78
78
 
79
79
  # The templates product:create, feature:create and the rest render. They are
80
80
  # data, not modules, so without this a wheel from PyPI shipped none of them.
81
+ # The build context of the frontend toolchain image too, so a CLI installed
82
+ # from PyPI can build the image a feature with frontend/ builds in.
81
83
  [tool.setuptools.package-data]
82
- splent_cli = ["templates/**/*"]
84
+ splent_cli = ["templates/**/*", "toolchain/**/*"]
83
85
 
84
86
  [tool.pytest.ini_options]
85
87
  testpaths = ["tests"]
@@ -1,12 +1,14 @@
1
+ import dataclasses
1
2
  import logging
2
3
  import os
3
4
  import posixpath
4
5
  import shlex
5
6
  import subprocess
7
+ from pathlib import Path
6
8
 
7
9
  import click
8
10
 
9
- from splent_cli.services import compose, context
11
+ from splent_cli.services import compose, context, frontend
10
12
  from splent_cli.utils.proc import run
11
13
  from splent_cli.utils.feature_utils import (
12
14
  get_features_from_pyproject,
@@ -196,17 +198,67 @@ def webpack_container(product: str, env: str, workspace: str) -> tuple[str | Non
196
198
  "assets/dist, and fail when the feature has nothing to build. "
197
199
  "feature:release runs it.",
198
200
  )
199
- def feature_compile(feature_name, watch, env_dev, env_prod, release_build):
201
+ @click.option(
202
+ "--verify",
203
+ is_flag=True,
204
+ help="Build nothing: check that assets/meta/manifest.json matches the "
205
+ "files of assets/dist and the lock, for each feature with frontend/.",
206
+ )
207
+ def feature_compile(feature_name, watch, env_dev, env_prod, release_build, verify):
200
208
  if release_build and watch:
201
209
  raise click.UsageError("--release builds the bundle once; drop --watch.")
210
+ if verify and (watch or release_build):
211
+ raise click.UsageError("--verify builds nothing; drop --watch and --release.")
202
212
  production = (
203
213
  os.getenv("FLASK_ENV", "develop") == "production" or env_prod or release_build
204
214
  )
215
+ workspace = str(context.workspace())
205
216
 
206
- features = (
207
- [_feature_ref(feature_name)] if feature_name else get_features_from_pyproject()
208
- )
217
+ # A feature with frontend/ of its own builds in the SPLENT toolchain and
218
+ # needs no product at all, so naming one is enough.
219
+ if feature_name and _frontend_root(workspace, _feature_ref(feature_name)):
220
+ features = [_feature_ref(feature_name)]
221
+ else:
222
+ features = (
223
+ [_feature_ref(feature_name)]
224
+ if feature_name
225
+ else get_features_from_pyproject()
226
+ )
209
227
 
228
+ if verify:
229
+ raise SystemExit(_verify_features(features, workspace))
230
+
231
+ frontend_features = [f for f in features if _frontend_root(workspace, f)]
232
+ legacy_features = [f for f in features if f not in frontend_features]
233
+ failed = [
234
+ feature
235
+ for feature in frontend_features
236
+ if not _compile_frontend(
237
+ _frontend_root(workspace, feature),
238
+ feature,
239
+ watch=watch,
240
+ production=production,
241
+ release_build=release_build,
242
+ )
243
+ ]
244
+
245
+ if legacy_features:
246
+ failed += _compile_legacy(
247
+ legacy_features, watch, production, env_dev, env_prod, release_build
248
+ )
249
+ if failed and not watch:
250
+ click.secho(
251
+ f"Compilation failed for {len(failed)} feature(s): {', '.join(failed)}",
252
+ fg="red",
253
+ err=True,
254
+ )
255
+ raise SystemExit(1)
256
+
257
+
258
+ def _compile_legacy(features, watch, production, env_dev, env_prod, release_build):
259
+ """Compile features without frontend/ (legacy mode): their own
260
+ webpack.config.js, run in the product's web container against the
261
+ product's node_modules. The features that failed."""
210
262
  env = context.resolve_env(env_dev, env_prod)
211
263
  product = context.require_app()
212
264
  workspace = str(context.workspace())
@@ -218,7 +270,7 @@ def feature_compile(feature_name, watch, env_dev, env_prod, release_build):
218
270
  # Every feature is attempted, so one run reports every broken bundle, and
219
271
  # the exit code then says whether any failed: the production Dockerfile
220
272
  # chains `splent feature:compile && ...` and must stop on a failure.
221
- failed = [
273
+ return [
222
274
  feature
223
275
  for feature in features
224
276
  if not _compile_in_container(
@@ -232,13 +284,246 @@ def feature_compile(feature_name, watch, env_dev, env_prod, release_build):
232
284
  release_build=release_build,
233
285
  )
234
286
  ]
235
- if failed and not watch:
287
+
288
+
289
+ def _split_ref(feature: str) -> tuple[str, str, str | None]:
290
+ """``(namespace, name, version)`` of a feature ref, splent_io by default."""
291
+ parts = feature.split("/")
292
+ if len(parts) == 2:
293
+ org_raw, name_version = parts
294
+ org_safe = normalize_namespace(org_raw)
295
+ else:
296
+ org_safe, name_version = "splent_io", parts[0]
297
+ base_name, _, version = name_version.partition("@")
298
+ return org_safe, base_name, version or None
299
+
300
+
301
+ def _frontend_root(workspace: str, feature: str) -> str | None:
302
+ """The working copy at the workspace root of an unpinned FEATURE when it
303
+ has frontend/ of its own, else None. A pinned one is never compiled."""
304
+ _, base_name, version = _split_ref(feature)
305
+ if version:
306
+ return None
307
+ root = os.path.join(workspace, base_name)
308
+ return root if frontend.declares(root) else None
309
+
310
+
311
+ def _compile_frontend(root, feature, *, watch, production, release_build) -> bool:
312
+ """Build one feature's frontend/ in the SPLENT toolchain. False only
313
+ when the build failed or could not run.
314
+
315
+ Inside the product's web container there is no Docker daemon to run the
316
+ toolchain in. Its startup script compiles every feature with --watch,
317
+ and a feature with frontend/ is left to the CLI there, with a note.
318
+ """
319
+ namespace, base_name, _ = _split_ref(feature)
320
+ if _is_product_container() and not release_build:
236
321
  click.secho(
237
- f"Compilation failed for {len(failed)} feature(s): {', '.join(failed)}",
238
- fg="red",
239
- err=True,
322
+ f" {feature} builds in the SPLENT toolchain, not in this container: "
323
+ f"run splent feature:compile {base_name} --watch from the CLI.",
324
+ fg="bright_black",
240
325
  )
241
- raise SystemExit(1)
326
+ return True
327
+
328
+ mode = "production" if production else "development"
329
+ click.echo(
330
+ click.style(f"🚀 Compiling {feature} in the SPLENT toolchain...", fg="cyan")
331
+ )
332
+ try:
333
+ image = frontend.ensure_toolchain()
334
+ frontend.install(root, image=image, force=release_build)
335
+ result = frontend.build(
336
+ root,
337
+ namespace,
338
+ base_name,
339
+ mode=mode,
340
+ watch=watch and not production,
341
+ image=image,
342
+ )
343
+ except click.ClickException as e:
344
+ click.secho(f"❌ Error compiling {feature}: {e.format_message()}", fg="red")
345
+ return False
346
+ if watch and not production:
347
+ if result.returncode != 0:
348
+ click.secho(
349
+ f"❌ The watch container of {feature} did not start:\n"
350
+ f"{(result.stderr or '').strip()}",
351
+ fg="red",
352
+ )
353
+ return False
354
+ name = frontend.watch_container(base_name)
355
+ click.echo(click.style(f"👀 Watching {feature} in {mode} mode...", fg="blue"))
356
+ click.echo(
357
+ click.style(
358
+ f" log: docker logs -f {name} stop: docker stop {name}",
359
+ fg="bright_black",
360
+ )
361
+ )
362
+ return True
363
+ if result.returncode != 0:
364
+ click.secho(f"❌ Error compiling {feature}: the build failed", fg="red")
365
+ return False
366
+ click.echo(
367
+ click.style(f"✅ Successfully compiled {feature} in {mode} mode!", fg="green")
368
+ )
369
+ return True
370
+
371
+
372
+ def _verify_features(features, workspace: str) -> int:
373
+ """feature:compile --verify: 1 when the bundle of any feature with
374
+ frontend/ does not match its manifest, else 0.
375
+
376
+ A working copy is checked at the workspace root, a pinned feature in the
377
+ copy the product links, which is what its app serves, and where neither
378
+ is, as in the production image, in the package pip installed. When the
379
+ product has docker/frontend.lock.json (product:build writes it), every
380
+ feature it lists is checked too, and has to carry the manifest the lock
381
+ names, of the version it names: the bundle its release built.
382
+ """
383
+ from splent_cli.utils.frontend_check import read_lock
384
+
385
+ product = context.active_app(workspace)
386
+ lock = read_lock(os.path.join(workspace, product)) if product else None
387
+ locked = (lock or {}).get("features") or {}
388
+ refs = list(features)
389
+ named = {_split_ref(feature)[1] for feature in refs}
390
+ for name, entry in sorted(locked.items()):
391
+ if name not in named:
392
+ version = entry.get("version")
393
+ namespace = entry.get("namespace") or "splent_io"
394
+ refs.append(f"{namespace}/{name}" + (f"@v{version}" if version else ""))
395
+
396
+ failed = 0
397
+ for feature in refs:
398
+ namespace, base_name, version = _split_ref(feature)
399
+ entry = locked.get(base_name)
400
+ copy = _copy_of(workspace, product, namespace, base_name, version)
401
+ if copy is None and entry is not None:
402
+ failed += 1
403
+ click.secho(
404
+ f"❌ {feature}: docker/frontend.lock.json lists it, and it is "
405
+ "neither here nor installed",
406
+ fg="red",
407
+ )
408
+ continue
409
+ if entry is None and (copy is None or not copy.declares):
410
+ click.echo(
411
+ click.style(
412
+ f" {feature} has no frontend/, nothing to verify.",
413
+ fg="bright_black",
414
+ )
415
+ )
416
+ continue
417
+ problems = frontend.verify_assets(
418
+ copy.assets, lock=copy.lock, production=bool(version) or copy.installed
419
+ )
420
+ if entry is not None:
421
+ problems += _against_lock(copy, entry, base_name)
422
+ if problems:
423
+ failed += 1
424
+ click.secho(
425
+ f"❌ {feature}: the bundle does not match its manifest", fg="red"
426
+ )
427
+ for problem in problems:
428
+ click.secho(f" {problem}", fg="red")
429
+ else:
430
+ click.secho(f"✅ {feature}: the bundle matches its manifest", fg="green")
431
+ return 1 if failed else 0
432
+
433
+
434
+ @dataclasses.dataclass
435
+ class _Copy:
436
+ """The copy of a feature --verify checks: its assets/, its frontend
437
+ lock (a package pip installed has none), whether pip installed it, and
438
+ whether it declares frontend/ (an installed one does when it carries a
439
+ manifest)."""
440
+
441
+ assets: Path
442
+ lock: Path | None
443
+ installed: bool
444
+ declares: bool
445
+
446
+
447
+ def _copy_of(workspace, product, namespace, base_name, version) -> _Copy | None:
448
+ """The working copy, the product's link to a pinned release, or else
449
+ the package pip installed. None when there is none."""
450
+ if version:
451
+ root = (
452
+ os.path.join(
453
+ workspace, product, "features", namespace, f"{base_name}@{version}"
454
+ )
455
+ if product
456
+ else None
457
+ )
458
+ else:
459
+ root = os.path.join(workspace, base_name)
460
+ if root and os.path.isdir(root):
461
+ return _Copy(
462
+ frontend.assets_dir(root, namespace, base_name),
463
+ Path(root) / frontend.FRONTEND_DIR / frontend.LOCK,
464
+ False,
465
+ frontend.declares(root),
466
+ )
467
+ import importlib.util
468
+
469
+ try:
470
+ spec = importlib.util.find_spec(f"{namespace}.{base_name}")
471
+ except (ImportError, ValueError):
472
+ spec = None
473
+ if spec is None or not spec.submodule_search_locations:
474
+ return None
475
+ assets = Path(list(spec.submodule_search_locations)[0]) / "assets"
476
+ return _Copy(assets, None, True, (assets / "meta" / "manifest.json").is_file())
477
+
478
+
479
+ def _against_lock(copy: _Copy, entry: dict, base_name: str) -> list[str]:
480
+ """What does not match between a feature's bundle and its entry in
481
+ docker/frontend.lock.json."""
482
+ problems = []
483
+ wanted = entry.get("manifest_sha256")
484
+ found = frontend.manifest_sha256(copy.assets)
485
+ if wanted and found and found != wanted:
486
+ problems.append(
487
+ "assets/meta/manifest.json is not the one docker/frontend.lock.json "
488
+ "names: run splent product:build"
489
+ )
490
+ if not copy.installed:
491
+ return problems
492
+ import importlib.metadata
493
+
494
+ distribution = entry.get("distribution") or base_name
495
+ try:
496
+ installed = importlib.metadata.version(distribution)
497
+ except importlib.metadata.PackageNotFoundError:
498
+ installed = None
499
+ if entry.get("version") and installed != entry["version"]:
500
+ problems.append(
501
+ f"pip installed {installed or 'no version'}, and "
502
+ f"docker/frontend.lock.json names {entry['version']}"
503
+ )
504
+ if found is None:
505
+ problems.append(
506
+ f"it was installed {_channel(distribution)}, without the bundle its "
507
+ "release built: release it again, so its wheel and its tag carry it"
508
+ )
509
+ return problems
510
+
511
+
512
+ def _channel(distribution: str) -> str:
513
+ """How pip installed DISTRIBUTION: from its git tag (git+) or a wheel."""
514
+ import importlib.metadata
515
+ import json
516
+
517
+ try:
518
+ direct = importlib.metadata.distribution(distribution).read_text(
519
+ "direct_url.json"
520
+ )
521
+ url = json.loads(direct or "{}")
522
+ except (importlib.metadata.PackageNotFoundError, ValueError):
523
+ url = {}
524
+ if "vcs_info" in url:
525
+ return f"from its git tag (git+{url.get('url', '')})"
526
+ return "from a wheel"
242
527
 
243
528
 
244
529
  def _feature_ref(name: str) -> str:
@@ -200,13 +200,24 @@ JS_FILES = {
200
200
  "assets/js/webpack.config.js": "feature/feature_webpack.config.js.j2",
201
201
  }
202
202
 
203
+ # What --frontend writes instead: the entry point under the package, and
204
+ # frontend/ at the root of the repository, whose package.json
205
+ # (services/frontend.py writes it) declares the bundle and its dependencies.
206
+ FRONTEND_SRC_FILES = {
207
+ "assets/js/scripts.js": "feature/feature_frontend_scripts.js.j2",
208
+ }
209
+ FRONTEND_BASE_FILES = {
210
+ "frontend/.npmrc": "feature/feature_frontend.npmrc.j2",
211
+ }
212
+
203
213
 
204
- def _build_file_map(feature_type, short_name, docker=False, js=False):
214
+ def _build_file_map(feature_type, short_name, docker=False, js=False, frontend=False):
205
215
  """Return (src_files, base_files) dicts for the given feature type.
206
216
 
207
217
  ``js`` adds the webpack entry point and config (JS_FILES). Code that
208
218
  recognises what feature:create wrote passes it: a feature created with
209
- --js, or by a CLI that always wrote them, has them.
219
+ --js, or by a CLI that always wrote them, has them. ``frontend`` adds the
220
+ entry point and frontend/ of a feature built by the SPLENT toolchain.
210
221
  """
211
222
  T = "feature/" # template prefix
212
223
 
@@ -309,6 +320,9 @@ def _build_file_map(feature_type, short_name, docker=False, js=False):
309
320
  # for nothing.
310
321
  if js:
311
322
  src = {**src, **JS_FILES}
323
+ if frontend:
324
+ src = {**src, **FRONTEND_SRC_FILES}
325
+ base_common = {**base_common, **FRONTEND_BASE_FILES}
312
326
 
313
327
  # Merge common files (type-specific wins on conflict)
314
328
  merged_src = {**src_common, **src}
@@ -341,13 +355,21 @@ def _build_file_map(feature_type, short_name, docker=False, js=False):
341
355
  help="Also scaffold assets/js (scripts.js and webpack.config.js) for a "
342
356
  "feature with a JavaScript bundle of its own.",
343
357
  )
358
+ @click.option(
359
+ "--frontend",
360
+ is_flag=True,
361
+ default=False,
362
+ help="Also scaffold frontend/ (package.json and .npmrc) and assets/js/scripts.js "
363
+ "for a feature whose bundle the SPLENT toolchain builds, with JavaScript "
364
+ "dependencies of its own.",
365
+ )
344
366
  @click.option(
345
367
  "--no-git",
346
368
  is_flag=True,
347
369
  default=False,
348
370
  help="Do not make the feature a git repository.",
349
371
  )
350
- def make_feature(full_name, feature_type, docker, js, no_git):
372
+ def make_feature(full_name, feature_type, docker, js, frontend, no_git):
351
373
  """
352
374
  Creates a new feature in the workspace.
353
375
  The name must follow the pattern <namespace>/<feature_name>.
@@ -371,6 +393,13 @@ def make_feature(full_name, feature_type, docker, js, no_git):
371
393
  the feature has no bundle, and nothing compiles one for it. Plain
372
394
  stylesheets and vendored libraries need neither (register_asset).
373
395
 
396
+ \b
397
+ --frontend adds frontend/package.json, which declares the bundle and the
398
+ npm packages it imports (splent feature:npm <feature> install <package>),
399
+ frontend/.npmrc and assets/js/scripts.js. The SPLENT toolchain builds it,
400
+ against the feature's own frontend/node_modules, with no webpack config
401
+ of the feature's. Not with --js, which writes the legacy one.
402
+
374
403
  \b
375
404
  The feature becomes a git repository on main, with the scaffold as its
376
405
  first commit made with your git identity, unless it is created inside a
@@ -384,7 +413,13 @@ def make_feature(full_name, feature_type, docker, js, no_git):
384
413
  splent feature:create splent-io/splent_feature_email_queue --type service
385
414
  splent feature:create splent-io/splent_feature_rabbitmq --type service --docker
386
415
  splent feature:create splent-io/splent_feature_charts --js
416
+ splent feature:create splent-io/splent_feature_charts --frontend
387
417
  """
418
+ if js and frontend:
419
+ raise click.UsageError(
420
+ "--js writes the legacy webpack.config.js and --frontend the "
421
+ "toolchain's frontend/: choose one."
422
+ )
388
423
 
389
424
  # --- Validate input pattern ---
390
425
  if "/" not in full_name:
@@ -452,11 +487,12 @@ def make_feature(full_name, feature_type, docker, js, no_git):
452
487
  # inline turns a line that has to be read literally into noise.
453
488
  "env_prefix": env_prefix,
454
489
  "host_port_ref": "${" + env_prefix + "_HOST_PORT}",
490
+ "frontend": frontend,
455
491
  }
456
492
 
457
493
  # --- Build file maps for the selected type ---
458
494
  src_files, base_files = _build_file_map(
459
- feature_type, short_name, docker=docker, js=js
495
+ feature_type, short_name, docker=docker, js=js, frontend=frontend
460
496
  )
461
497
 
462
498
  # --- Create src structure ---
@@ -479,6 +515,14 @@ def make_feature(full_name, feature_type, docker, js, no_git):
479
515
 
480
516
  write_license_files(feature_dir)
481
517
 
518
+ if frontend:
519
+ from splent_cli.services import frontend as toolchain
520
+
521
+ toolchain.write_package(
522
+ feature_dir,
523
+ toolchain.new_package(org_safe, feature_name, license_="LGPL-3.0-or-later"),
524
+ )
525
+
482
526
  # --- src/__init__.py (namespace root) ---
483
527
  src_root = os.path.join(feature_dir, "src")
484
528
  os.makedirs(src_root, exist_ok=True)
@@ -517,6 +561,16 @@ def make_feature(full_name, feature_type, docker, js, no_git):
517
561
  _report_git(*git_state, feature_dir)
518
562
  click.echo()
519
563
 
564
+ if frontend:
565
+ click.echo(
566
+ click.style(" frontend/ ", fg="cyan")
567
+ + "add a dependency with "
568
+ + click.style(f"feature:npm {short_name} install <package>", bold=True)
569
+ + ", build with "
570
+ + click.style(f"feature:compile {short_name}", bold=True)
571
+ )
572
+ click.echo()
573
+
520
574
  if docker:
521
575
  click.echo(
522
576
  click.style(" docker/ ", fg="cyan")
@@ -7,7 +7,7 @@ from splent_cli.services import context
7
7
  from splent_cli.utils.feature_resolution import resolve_feature_ref
8
8
  from splent_cli.utils.template_drift import (
9
9
  CLI_VERSION,
10
- FEATURE_FILES,
10
+ feature_files,
11
11
  count_changed_lines,
12
12
  feature_ctx,
13
13
  file_diff,
@@ -74,7 +74,8 @@ def _print_diff_lines(diff: list[str]) -> None:
74
74
  def feature_drift(feature_identifier, verbose):
75
75
  """
76
76
  Compare SPLENT-owned files in a feature against the current templates.
77
- Only scaffold files are checked (webpack.config.js, MANIFEST.in, .gitignore).
77
+ Only scaffold files are checked (webpack.config.js, MANIFEST.in, .gitignore;
78
+ for a feature with frontend/, MANIFEST.in, .gitignore and frontend/.npmrc).
78
79
  Developer-owned source files are never touched.
79
80
  Does not modify any files. Reads the editable copy at the workspace
80
81
  root, or else the snapshot the product pins (or the newest one).
@@ -125,10 +126,11 @@ def feature_drift(feature_identifier, verbose):
125
126
  )
126
127
  )
127
128
 
128
- ctx = feature_ctx(org_safe, feature_name)
129
+ files, extra = feature_files(cache_path)
130
+ ctx = feature_ctx(org_safe, feature_name, **extra)
129
131
  any_drift = False
130
132
 
131
- for rel_tpl, tpl_name in FEATURE_FILES.items():
133
+ for rel_tpl, tpl_name in files.items():
132
134
  rel_path = resolve_feature_rel(rel_tpl, org_safe, feature_name)
133
135
  abs_path = cache_path / rel_path
134
136
  expected = render_template(tpl_name, ctx)