simple-module-cli 0.0.36__tar.gz → 0.0.38__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 (186) hide show
  1. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/.gitignore +2 -2
  2. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/PKG-INFO +1 -1
  3. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/pyproject.toml +1 -1
  4. simple_module_cli-0.0.38/simple_module_cli/_add_install.py +59 -0
  5. simple_module_cli-0.0.38/simple_module_cli/_add_sources.py +83 -0
  6. simple_module_cli-0.0.38/simple_module_cli/_app_project_manifests.py +94 -0
  7. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_env.py +8 -1
  8. simple_module_cli-0.0.38/simple_module_cli/_local_env.py +83 -0
  9. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/add_cmd.py +7 -89
  10. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/app_project.py +38 -96
  11. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/case.py +26 -0
  12. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/catalog.py +3 -7
  13. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/cli.py +33 -0
  14. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/docker_assets.py +3 -2
  15. simple_module_cli-0.0.38/simple_module_cli/docker_cmd.py +158 -0
  16. simple_module_cli-0.0.38/simple_module_cli/framework_repo.py +127 -0
  17. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/new.py +1 -0
  18. simple_module_cli-0.0.38/simple_module_cli/openapi_cmd.py +105 -0
  19. simple_module_cli-0.0.38/simple_module_cli/openapi_types.py +205 -0
  20. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/pyproject_edit.py +19 -10
  21. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/scaffolding.py +26 -1
  22. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-conventions/SKILL.md +20 -6
  23. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-creating/SKILL.md +24 -12
  24. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-doctor/SKILL.md +6 -2
  25. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-inertia-pages/SKILL.md +16 -7
  26. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-testing/SKILL.md +2 -2
  27. simple_module_cli-0.0.38/simple_module_cli/templates/docker/Makefile.snippet +25 -0
  28. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-base-postgres.yml.tpl +5 -3
  29. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-tasks-postgres.yml.tpl +3 -3
  30. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-tasks-sqlite.yml.tpl +1 -1
  31. simple_module_cli-0.0.38/simple_module_cli/templates/docker/compose-worker.yml.tpl +12 -0
  32. simple_module_cli-0.0.38/simple_module_cli/templates/docker/worker.Dockerfile.tpl +27 -0
  33. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/.env.example +3 -2
  34. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/.gitignore +1 -3
  35. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/Makefile +21 -2
  36. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/README.md.tpl +1 -1
  37. simple_module_cli-0.0.38/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +24 -0
  38. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/app.tsx +14 -2
  39. simple_module_cli-0.0.38/simple_module_cli/templates/host/client_app/pages/Error.tsx +1 -0
  40. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/env.py +9 -0
  41. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/routes.py +1 -1
  42. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/templates/index.html +1 -1
  43. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/constants.py.tpl +36 -0
  44. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/contracts/__init__.py.tpl +13 -0
  45. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/contracts/schemas.py.tpl +46 -0
  46. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/deps.py.tpl +15 -0
  47. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +91 -0
  48. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/endpoints/views.py.tpl +95 -0
  49. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/locales/en.json.tpl +27 -0
  50. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/models.py.tpl +26 -0
  51. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +104 -0
  52. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Browse.tsx.tpl +57 -0
  53. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Create.tsx.tpl +43 -0
  54. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Edit.tsx.tpl +46 -0
  55. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/api.generated.ts.tpl +266 -0
  56. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/api.ts.tpl +10 -0
  57. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/py.typed +0 -0
  58. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/service.py.tpl +60 -0
  59. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +17 -0
  60. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/LICENSE +21 -0
  61. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/README.md.tpl +34 -0
  62. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Browse.tsx.tpl +63 -0
  63. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Create.tsx.tpl +45 -0
  64. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Edit.tsx.tpl +48 -0
  65. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/pyproject.toml.tpl +44 -0
  66. simple_module_cli-0.0.38/simple_module_cli/templates/module/tests/test___PACKAGE__.py.tpl +126 -0
  67. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/.env.example +3 -2
  68. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/.gitignore +1 -3
  69. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/Makefile +19 -6
  70. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/README.md.tpl +9 -10
  71. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/pyproject.toml.tpl +3 -0
  72. simple_module_cli-0.0.38/simple_module_cli/uv_workspace.py +57 -0
  73. simple_module_cli-0.0.38/tests/test_cli_add_workspace_member.py +112 -0
  74. simple_module_cli-0.0.38/tests/test_cli_catalog_depends_on.py +49 -0
  75. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_docker_assets.py +82 -2
  76. simple_module_cli-0.0.38/tests/test_cli_docker_workers.py +162 -0
  77. simple_module_cli-0.0.38/tests/test_cli_gen_types_scaffold.py +65 -0
  78. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new.py +55 -19
  79. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_regressions.py +3 -1
  80. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_scaffold_layout.py +32 -0
  81. simple_module_cli-0.0.38/tests/test_cli_openapi_export.py +32 -0
  82. simple_module_cli-0.0.38/tests/test_cli_openapi_gen_types.py +214 -0
  83. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_recipes.py +16 -2
  84. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_wizard.py +4 -4
  85. simple_module_cli-0.0.38/tests/test_framework_repo_wiring.py +125 -0
  86. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_css_build.py +3 -0
  87. simple_module_cli-0.0.38/tests/test_scaffold_app_tsx_spa_links.py +9 -0
  88. simple_module_cli-0.0.38/tests/test_scaffolding_framework_repo.py +140 -0
  89. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolding_host.py +2 -0
  90. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolding_module.py +1 -1
  91. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolding_module_js.py +7 -4
  92. simple_module_cli-0.0.38/tests/test_template_requires_framework.py +24 -0
  93. simple_module_cli-0.0.36/simple_module_cli/templates/docker/Makefile.snippet +0 -13
  94. simple_module_cli-0.0.36/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +0 -26
  95. simple_module_cli-0.0.36/simple_module_cli/templates/host/client_app/pages/Error.tsx +0 -13
  96. simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +0 -11
  97. simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/endpoints/views.py.tpl +0 -16
  98. simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +0 -59
  99. simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/pages/Index.tsx.tpl +0 -13
  100. simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +0 -14
  101. simple_module_cli-0.0.36/simple_module_cli/templates/module/tests/test___PACKAGE__.py.tpl +0 -30
  102. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/LICENSE +0 -0
  103. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/README.md +0 -0
  104. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/__init__.py +0 -0
  105. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_module_build.py +0 -0
  106. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_module_host.py +0 -0
  107. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_templating.py +0 -0
  108. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/git_source.py +0 -0
  109. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/module_cmd.py +0 -0
  110. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/package_update.py +0 -0
  111. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/pins.py +0 -0
  112. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/plugins.py +0 -0
  113. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/pypi.py +0 -0
  114. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/recipes.py +0 -0
  115. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/requirements.py +0 -0
  116. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/README.md +0 -0
  117. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-cli/SKILL.md +0 -0
  118. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-database/SKILL.md +0 -0
  119. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-locales/SKILL.md +0 -0
  120. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-migrations/SKILL.md +0 -0
  121. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-registries/SKILL.md +0 -0
  122. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills_cmd.py +0 -0
  123. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-base-sqlite.yml.tpl +0 -0
  124. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/dockerignore +0 -0
  125. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/host-flat.Dockerfile +0 -0
  126. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/host.Dockerfile +0 -0
  127. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/_optional/background_tasks/Makefile.snippet +0 -0
  128. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/alembic.ini +0 -0
  129. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/main.tsx +0 -0
  130. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/module-assets.ts +0 -0
  131. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/package.json.tpl +0 -0
  132. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/pages/Landing.tsx +0 -0
  133. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/pages.ts +0 -0
  134. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/styles.css +0 -0
  135. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/tsconfig.json +0 -0
  136. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/vite.config.ts +0 -0
  137. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/vite.dev-url.ts +0 -0
  138. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/main.py +0 -0
  139. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/script.py.mako +0 -0
  140. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/versions/.gitkeep +0 -0
  141. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/pyproject.toml.tpl +0 -0
  142. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.github/workflows/ci.yml +0 -0
  143. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.github/workflows/publish.yml.tpl +0 -0
  144. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.gitignore +0 -0
  145. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/README.md.tpl +0 -0
  146. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/__init__.py +0 -0
  147. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/endpoints/__init__.py +0 -0
  148. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/services.py.tpl +0 -0
  149. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/_optional/standalone/package.json.tpl +0 -0
  150. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/_optional/standalone/tsconfig.json.tpl +0 -0
  151. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/package.json.tpl +0 -0
  152. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/pyproject.toml.tpl +0 -0
  153. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/tsconfig.json.tpl +0 -0
  154. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/package.json.tpl +0 -0
  155. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/update_cmd.py +0 -0
  156. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/wizard.py +0 -0
  157. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/conftest.py +0 -0
  158. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_build_packaging.py +0 -0
  159. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_case.py +0 -0
  160. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_add_cmd.py +0 -0
  161. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_add_spec_parse.py +0 -0
  162. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_catalog.py +0 -0
  163. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_git_scan.py +0 -0
  164. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_dest_tolerance.py +0 -0
  165. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_git_module.py +0 -0
  166. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_migration_bootstrap.py +0 -0
  167. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_package_update.py +0 -0
  168. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_package_update_pins.py +0 -0
  169. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_pyproject_edit.py +0 -0
  170. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_update_sources.py +0 -0
  171. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_vite_port_env.py +0 -0
  172. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_wizard_git.py +0 -0
  173. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_create_module_ci_skip.py +0 -0
  174. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_env_helper.py +0 -0
  175. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_cmd_build.py +0 -0
  176. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_cmd_e2e.py +0 -0
  177. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_cmd_verify.py +0 -0
  178. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_css_assets.py +0 -0
  179. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_css_subdir_sources.py +0 -0
  180. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_npm_aliases.py +0 -0
  181. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_pages_manifest.py +0 -0
  182. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_no_framework_deps.py +0 -0
  183. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_plugin_discovery.py +0 -0
  184. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffold_rollback.py +0 -0
  185. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolded_host_setup_wizard.py +0 -0
  186. {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_skills_cmd.py +0 -0
@@ -7,8 +7,6 @@ build/
7
7
  .venv/
8
8
  *.egg
9
9
 
10
- # UV
11
- uv.lock
12
10
 
13
11
  # Node
14
12
  node_modules/
@@ -64,3 +62,5 @@ host/client_app/.playwright-cli/*
64
62
  .superpowers/
65
63
  .qa/
66
64
  .verify/
65
+ /openapi.json
66
+ qa-shots/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: simple_module_cli
3
- Version: 0.0.36
3
+ Version: 0.0.38
4
4
  Summary: Standalone scaffolder for the SimpleModule framework — `smpy new`, `smpy create-module`, plugin host.
5
5
  Project-URL: Homepage, https://github.com/antosubash/simple_module_python
6
6
  Project-URL: Repository, https://github.com/antosubash/simple_module_python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simple_module_cli"
3
- version = "0.0.36"
3
+ version = "0.0.38"
4
4
  description = "Standalone scaffolder for the SimpleModule framework — `smpy new`, `smpy create-module`, plugin host."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,59 @@
1
+ """Post-install steps and shared helpers for ``smpy add``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from collections.abc import Callable
7
+ from pathlib import Path
8
+
9
+ import typer
10
+
11
+ ExecRunner = Callable[[list[str], Path], int]
12
+
13
+
14
+ _ENTRYPOINT_CHECK = (
15
+ "import sys\n"
16
+ "from importlib.metadata import distributions\n"
17
+ "target = sys.argv[1].replace('-', '_').lower()\n"
18
+ "for dist in distributions():\n"
19
+ " if any(ep.group == 'simple_module' for ep in dist.entry_points):\n"
20
+ " name = (dist.metadata['Name'] or '').replace('-', '_').lower()\n"
21
+ " if name == target:\n"
22
+ " sys.exit(0)\n"
23
+ "sys.exit(1)\n"
24
+ )
25
+
26
+
27
+ def _default_exec_runner(cmd: list[str], cwd: Path) -> int:
28
+ return subprocess.run(cmd, cwd=cwd, check=False).returncode
29
+
30
+
31
+ def _fail(message: str) -> typer.Exit:
32
+ typer.echo(f"ERROR: {message}", err=True)
33
+ return typer.Exit(code=1)
34
+
35
+
36
+ def post_install(
37
+ project_dir: Path,
38
+ dists: list[str],
39
+ models_dists: list[str],
40
+ exec_runner: ExecRunner,
41
+ ) -> None:
42
+ """uv sync → gen-pages → sync-js-deps → entry-point check → reminders."""
43
+ if exec_runner(["uv", "sync"], project_dir) != 0:
44
+ raise _fail("`uv sync` failed — pyproject was written; fix and re-run `uv sync`.")
45
+ if (project_dir / "client_app").is_dir():
46
+ exec_runner(["uv", "run", "smpy", "host", "gen-pages"], project_dir)
47
+ exec_runner(["uv", "run", "smpy", "host", "sync-js-deps"], project_dir)
48
+ for dist in dists:
49
+ code = exec_runner(["uv", "run", "python", "-c", _ENTRYPOINT_CHECK, dist], project_dir)
50
+ if code != 0:
51
+ raise _fail(
52
+ f"{dist} installed but exposes no [project.entry-points.simple_module] "
53
+ "entry point — it is not a SimpleModule module."
54
+ )
55
+ for dist in models_dists:
56
+ typer.echo(
57
+ f"{dist} ships database models — generate and apply the migration:\n"
58
+ f' make migration msg="add {dist}"\n make migrate'
59
+ )
@@ -0,0 +1,83 @@
1
+ """PyPI and local-path sources for ``smpy add``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ import typer
9
+
10
+ from simple_module_cli._add_install import ExecRunner, _fail, post_install
11
+ from simple_module_cli.git_source import derive_range, scan_modules
12
+ from simple_module_cli.pyproject_edit import (
13
+ save_pyproject,
14
+ write_dependency,
15
+ write_path_source,
16
+ write_workspace_source,
17
+ )
18
+ from simple_module_cli.uv_workspace import is_workspace_member
19
+
20
+
21
+ def _add_pypi(doc, pyproject: Path, raw: str, no_sync: bool, exec_runner: ExecRunner) -> list[str]:
22
+ name = raw
23
+ for i, ch in enumerate(raw):
24
+ if ch in "<>=!~[; ":
25
+ name = raw[:i]
26
+ break
27
+ constraint = raw[len(name) :]
28
+ write_dependency(doc, name, constraint)
29
+ save_pyproject(pyproject, doc)
30
+ typer.echo(f"Added {name} (PyPI).")
31
+ if not no_sync:
32
+ post_install(pyproject.parent, [name], [], exec_runner)
33
+ return [name]
34
+
35
+
36
+ def _resolve_local_path(project_dir: Path, spec_path: Path) -> tuple[Path, str]:
37
+ """Resolve a local-path spec to ``(directory, text written to pyproject)``.
38
+
39
+ A relative path is resolved against the ``--path`` project (the pyproject
40
+ being edited, which is what ``[tool.uv.sources]`` paths are relative to).
41
+ If nothing exists there but the path exists relative to the cwd, the cwd
42
+ one is used and the written path is re-expressed relative to the project.
43
+ """
44
+ if spec_path.is_absolute():
45
+ return spec_path, str(spec_path)
46
+ root = project_dir / spec_path
47
+ if not root.exists() and (Path.cwd() / spec_path).exists():
48
+ root = Path.cwd() / spec_path
49
+ return root, os.path.relpath(root.resolve(), project_dir.resolve())
50
+ return root, str(spec_path)
51
+
52
+
53
+ def _add_path(
54
+ doc, pyproject: Path, spec_path: Path, no_sync: bool, exec_runner: ExecRunner
55
+ ) -> list[str]:
56
+ root, written = _resolve_local_path(pyproject.parent, spec_path)
57
+ found = [m for m in scan_modules(root) if m.subdirectory is None]
58
+ if not found:
59
+ raise _fail(
60
+ f"{root} has no [project.entry-points.simple_module] entry point at its root "
61
+ f"(relative paths resolve against the --path project, {pyproject.parent}, "
62
+ "falling back to the current directory)"
63
+ )
64
+ mod = found[0]
65
+ if is_workspace_member(pyproject.parent, root):
66
+ # uv refuses a path source for a workspace member; the scaffold's own
67
+ # sample module is wired the same way (unpinned + workspace = true).
68
+ write_dependency(doc, mod.dist_name, "")
69
+ write_workspace_source(doc, mod.dist_name)
70
+ typer.echo(f"Added {mod.dist_name} (uv workspace member {written}).")
71
+ else:
72
+ write_dependency(doc, mod.dist_name, derive_range(mod.version))
73
+ write_path_source(doc, mod.dist_name, path=written)
74
+ typer.echo(f"Added {mod.dist_name} (editable path {written}).")
75
+ save_pyproject(pyproject, doc)
76
+ if not no_sync:
77
+ post_install(
78
+ pyproject.parent,
79
+ [mod.dist_name],
80
+ [mod.dist_name] if mod.ships_models else [],
81
+ exec_runner,
82
+ )
83
+ return [mod.dist_name]
@@ -0,0 +1,94 @@
1
+ """Dependency pins, ``.env`` DB URLs and ``pyproject.toml`` rewriting for ``smpy new``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+
7
+ from simple_module_cli.scaffolding import resolve_framework_version
8
+
9
+ _FRAMEWORK_VERSION = resolve_framework_version()
10
+
11
+ # Pin ``simple_module_cli`` as a dev dep so ``uv run smpy`` resolves to the
12
+ # project venv. The global ``uv tool`` install runs in its own isolated venv
13
+ # that can't see the project's plugin entry points (issue #134). The lint /
14
+ # test tooling (ruff, ty, pytest-*) backs the generated `make lint`/`make
15
+ # test` targets so a fresh app can run its own quality gates.
16
+ _APP_PY_DEV_DEPS = [
17
+ f"simple_module_test=={_FRAMEWORK_VERSION}",
18
+ f"simple_module_cli=={_FRAMEWORK_VERSION}",
19
+ "pytest>=8.0",
20
+ "pytest-asyncio>=0.24",
21
+ "pytest-playwright>=0.7.2",
22
+ # Floor: unpinned, uv backtracks playwright to 1.18 (GH #445).
23
+ "playwright>=1.45",
24
+ "ruff>=0.8",
25
+ "ty>=0.0.29",
26
+ ]
27
+
28
+ _APP_NPM_DEPS = {
29
+ "@simple-module-py/ui": _FRAMEWORK_VERSION,
30
+ "@simple-module-py/i18n": _FRAMEWORK_VERSION,
31
+ "react": "^19.0.0",
32
+ "react-dom": "^19.0.0",
33
+ "@inertiajs/react": "^3.7.0",
34
+ }
35
+ # Flat mode has no npm workspaces, so `client_app/package.json` is never
36
+ # installed — the root package.json seeded from these two dicts is the only
37
+ # manifest npm reads. It must therefore cover everything client_app needs to
38
+ # build, not just the framework pins: `vite.config.ts` imports
39
+ # `@tailwindcss/vite`, and `tsc` needs the @types packages. Keep this in step
40
+ # with templates/host/client_app/package.json.tpl.
41
+ _APP_NPM_DEV_DEPS = {
42
+ "@simple-module-py/tsconfig": _FRAMEWORK_VERSION,
43
+ "@tailwindcss/vite": "^4.0.0",
44
+ "@types/node": "^26.5.0",
45
+ "@types/react": "^19.0.0",
46
+ "@types/react-dom": "^19.0.0",
47
+ "@vitejs/plugin-react": "^6.1.1",
48
+ "tailwindcss": "^4.0.0",
49
+ "typescript": "^7.0.2",
50
+ "vite": "^8.2.2",
51
+ }
52
+
53
+
54
+ def _db_url(db: str, db_name: str, *, flat: bool, password: str = "<POSTGRES_PASSWORD>") -> str:
55
+ if db == "postgres":
56
+ return f"postgresql+asyncpg://postgres:{password}@localhost:5432/{db_name}"
57
+ # Workspace mode keeps the SQLite file next to host/'s alembic.ini so
58
+ # `cd host && uvicorn` and `cd host && alembic` resolve the same path.
59
+ return "sqlite+aiosqlite:///./app.db" if flat else "sqlite+aiosqlite:///./host/app.db"
60
+
61
+
62
+ def _rewrite_pyproject(
63
+ text: str,
64
+ deps: list[str],
65
+ dev_deps: list[str],
66
+ *,
67
+ sources: Sequence[str] = (),
68
+ project_name: str | None = None,
69
+ ) -> str:
70
+ """Replace deps in a host ``pyproject.toml`` and pin workspace sources.
71
+
72
+ ``sources`` lists ``simple_module_*`` packages that should resolve from
73
+ the uv workspace (``modules/*``) instead of PyPI. Emits a
74
+ ``[tool.uv.sources]`` block per entry. Empty in flat mode.
75
+
76
+ ``project_name`` overrides ``[project].name`` — set in workspace mode
77
+ so the host's package name differs from the workspace root's.
78
+ """
79
+ import tomlkit
80
+
81
+ doc = tomlkit.parse(text)
82
+ project = doc.setdefault("project", tomlkit.table())
83
+ if project_name is not None:
84
+ project["name"] = project_name
85
+ project["dependencies"] = list(deps)
86
+ groups = doc.setdefault("dependency-groups", tomlkit.table())
87
+ groups["dev"] = list(dev_deps)
88
+ if sources:
89
+ tool = doc.setdefault("tool", tomlkit.table())
90
+ uv_table = tool.setdefault("uv", tomlkit.table())
91
+ uv_sources = uv_table.setdefault("sources", tomlkit.table())
92
+ for src in sources:
93
+ uv_sources[src] = {"workspace": True}
94
+ return tomlkit.dumps(doc)
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __all__ = ["set_env_key"]
5
+ __all__ = ["env_values", "set_env_key"]
6
6
 
7
7
 
8
8
  def set_env_key(text: str, key: str, value: str) -> str:
@@ -10,3 +10,10 @@ def set_env_key(text: str, key: str, value: str) -> str:
10
10
  lines = [ln for ln in text.splitlines() if not ln.startswith(f"{key}=")]
11
11
  lines.append(f"{key}={value}")
12
12
  return "\n".join(lines) + "\n"
13
+
14
+
15
+ def env_values(text: str) -> dict[str, str]:
16
+ """Parse the ``KEY=VALUE`` lines of an env-style file body (comments skipped)."""
17
+ lines = text.splitlines()
18
+ pairs = (ln.split("=", 1) for ln in lines if "=" in ln and not ln.lstrip().startswith("#"))
19
+ return {key.strip(): value for key, value in pairs}
@@ -0,0 +1,83 @@
1
+ """Split a new app's env file into a committed template and a local ``.env``.
2
+
3
+ ``.env.example`` is committed, so it only ever carries placeholders. The
4
+ secrets ``smpy new`` generates go to ``.env``, which every scaffold's
5
+ ``.gitignore`` excludes (GH #441). An existing ``.env`` is never overwritten:
6
+ only keys it does not define yet are added.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import secrets
12
+ from collections.abc import Iterable
13
+ from pathlib import Path
14
+
15
+ from simple_module_cli._env import env_values, set_env_key
16
+
17
+ __all__ = ["add_postgres_password_placeholder", "comment_out_env_key", "write_local_env"]
18
+
19
+ _PG_PASSWORD_NOTE = (
20
+ "# Compose puts this into SM_DATABASE_URL unencoded: use only URL-safe\n"
21
+ "# characters (letters, digits, - _ . ~). `make docker-up` rejects others (GH #472).\n"
22
+ )
23
+
24
+ _SECRET_NOTE = (
25
+ "# Secrets — `smpy new` generated these into .env (gitignored); keep them\n"
26
+ "# out of this committed file. Set real values wherever you deploy."
27
+ )
28
+
29
+
30
+ def comment_out_env_key(text: str, key: str) -> str:
31
+ """Turn ``KEY=...`` into a bare ``# KEY=`` placeholder, in place if present."""
32
+ lines = text.splitlines()
33
+ placeholder = f"# {key}="
34
+ hits = [i for i, ln in enumerate(lines) if ln.startswith((f"{key}=", placeholder))]
35
+ if hits:
36
+ lines[hits[0]] = placeholder
37
+ lines = [ln for i, ln in enumerate(lines) if i not in hits[1:]]
38
+ else:
39
+ if _SECRET_NOTE.splitlines()[0] not in lines:
40
+ lines += ["", *_SECRET_NOTE.splitlines()]
41
+ lines.append(placeholder)
42
+ return "\n".join(lines) + "\n"
43
+
44
+
45
+ def add_postgres_password_placeholder(text: str) -> str:
46
+ """Add an empty ``POSTGRES_PASSWORD=`` to ``.env.example``, with its URL-safe note."""
47
+ text = set_env_key(text, "POSTGRES_PASSWORD", "")
48
+ return text.replace("POSTGRES_PASSWORD=\n", _PG_PASSWORD_NOTE + "POSTGRES_PASSWORD=\n", 1)
49
+
50
+
51
+ def write_local_env(
52
+ target: Path, *, secret_keys: Iterable[str], overrides: dict[str, str] | None = None
53
+ ) -> None:
54
+ """Write ``target/.env`` from ``.env.example`` plus generated secrets.
55
+
56
+ ``secret_keys`` get a fresh random value each; ``overrides`` are written
57
+ verbatim (e.g. a database URL carrying a generated password). Overrides
58
+ belong together, so an existing ``.env`` defining any of them gets none:
59
+ a fresh URL must not disagree with a kept ``POSTGRES_PASSWORD``.
60
+ """
61
+ env_path = target / ".env"
62
+ existing = env_path.exists()
63
+ source = env_path if existing else target / ".env.example"
64
+ text = source.read_text(encoding="utf-8") if source.exists() else ""
65
+ defined = env_values(text).keys()
66
+ overrides = overrides or {}
67
+ if existing and defined & overrides.keys():
68
+ overrides = {}
69
+ values = {key: secrets.token_urlsafe(32) for key in secret_keys} | overrides
70
+ for key, value in values.items():
71
+ if existing and key in defined:
72
+ continue
73
+ placeholder = f"# {key}="
74
+ lines = text.splitlines()
75
+ if placeholder in lines:
76
+ # Line-based, so a placeholder on the last line (no trailing
77
+ # newline) is handled too.
78
+ lines[lines.index(placeholder)] = f"{key}={value}"
79
+ text = "\n".join(lines) + "\n"
80
+ else:
81
+ text = set_env_key(text, key, value)
82
+ env_path.write_text(text, encoding="utf-8")
83
+ env_path.chmod(0o600) # holds generated secrets
@@ -11,14 +11,19 @@ sync-js-deps`` → entry-point verification, unless ``--no-sync``.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- import subprocess
15
14
  import tempfile
16
- from collections.abc import Callable
17
15
  from pathlib import Path
18
16
  from typing import Annotated
19
17
 
20
18
  import typer
21
19
 
20
+ from simple_module_cli._add_install import (
21
+ ExecRunner,
22
+ _default_exec_runner,
23
+ _fail,
24
+ post_install,
25
+ )
26
+ from simple_module_cli._add_sources import _add_path, _add_pypi
22
27
  from simple_module_cli.git_source import (
23
28
  FoundModule,
24
29
  SpecError,
@@ -34,39 +39,15 @@ from simple_module_cli.pyproject_edit import (
34
39
  save_pyproject,
35
40
  write_dependency,
36
41
  write_git_source,
37
- write_path_source,
38
42
  )
39
43
 
40
44
  __all__ = ["ExecRunner", "add_module", "post_install", "run_add"]
41
45
 
42
- ExecRunner = Callable[[list[str], Path], int]
43
-
44
46
  _SECURITY_NOTICE = (
45
47
  "note: this installs and runs code from a URL you chose — review the "
46
48
  "repository before booting the host."
47
49
  )
48
50
 
49
- _ENTRYPOINT_CHECK = (
50
- "import sys\n"
51
- "from importlib.metadata import distributions\n"
52
- "target = sys.argv[1].replace('-', '_').lower()\n"
53
- "for dist in distributions():\n"
54
- " if any(ep.group == 'simple_module' for ep in dist.entry_points):\n"
55
- " name = (dist.metadata['Name'] or '').replace('-', '_').lower()\n"
56
- " if name == target:\n"
57
- " sys.exit(0)\n"
58
- "sys.exit(1)\n"
59
- )
60
-
61
-
62
- def _default_exec_runner(cmd: list[str], cwd: Path) -> int:
63
- return subprocess.run(cmd, cwd=cwd, check=False).returncode
64
-
65
-
66
- def _fail(message: str) -> typer.Exit:
67
- typer.echo(f"ERROR: {message}", err=True)
68
- return typer.Exit(code=1)
69
-
70
51
 
71
52
  def _choose(
72
53
  found: list[FoundModule],
@@ -96,69 +77,6 @@ def _choose(
96
77
  ]
97
78
 
98
79
 
99
- def post_install(
100
- project_dir: Path,
101
- dists: list[str],
102
- models_dists: list[str],
103
- exec_runner: ExecRunner,
104
- ) -> None:
105
- """uv sync → gen-pages → sync-js-deps → entry-point check → reminders."""
106
- if exec_runner(["uv", "sync"], project_dir) != 0:
107
- raise _fail("`uv sync` failed — pyproject was written; fix and re-run `uv sync`.")
108
- if (project_dir / "client_app").is_dir():
109
- exec_runner(["uv", "run", "smpy", "host", "gen-pages"], project_dir)
110
- exec_runner(["uv", "run", "smpy", "host", "sync-js-deps"], project_dir)
111
- for dist in dists:
112
- code = exec_runner(["uv", "run", "python", "-c", _ENTRYPOINT_CHECK, dist], project_dir)
113
- if code != 0:
114
- raise _fail(
115
- f"{dist} installed but exposes no [project.entry-points.simple_module] "
116
- "entry point — it is not a SimpleModule module."
117
- )
118
- for dist in models_dists:
119
- typer.echo(
120
- f"{dist} ships database models — generate and apply the migration:\n"
121
- f' make migration msg="add {dist}"\n make migrate'
122
- )
123
-
124
-
125
- def _add_pypi(doc, pyproject: Path, raw: str, no_sync: bool, exec_runner: ExecRunner) -> list[str]:
126
- name = raw
127
- for i, ch in enumerate(raw):
128
- if ch in "<>=!~[; ":
129
- name = raw[:i]
130
- break
131
- constraint = raw[len(name) :]
132
- write_dependency(doc, name, constraint)
133
- save_pyproject(pyproject, doc)
134
- typer.echo(f"Added {name} (PyPI).")
135
- if not no_sync:
136
- post_install(pyproject.parent, [name], [], exec_runner)
137
- return [name]
138
-
139
-
140
- def _add_path(
141
- doc, pyproject: Path, spec_path: Path, no_sync: bool, exec_runner: ExecRunner
142
- ) -> list[str]:
143
- root = spec_path if spec_path.is_absolute() else pyproject.parent / spec_path
144
- found = [m for m in scan_modules(root) if m.subdirectory is None]
145
- if not found:
146
- raise _fail(f"{root} has no [project.entry-points.simple_module] entry point at its root")
147
- mod = found[0]
148
- write_dependency(doc, mod.dist_name, derive_range(mod.version))
149
- write_path_source(doc, mod.dist_name, path=str(spec_path))
150
- save_pyproject(pyproject, doc)
151
- typer.echo(f"Added {mod.dist_name} (editable path {spec_path}).")
152
- if not no_sync:
153
- post_install(
154
- pyproject.parent,
155
- [mod.dist_name],
156
- [mod.dist_name] if mod.ships_models else [],
157
- exec_runner,
158
- )
159
- return [mod.dist_name]
160
-
161
-
162
80
  def run_add(
163
81
  spec: str,
164
82
  *,
@@ -19,8 +19,21 @@ from collections.abc import Sequence
19
19
  from pathlib import Path
20
20
  from typing import Any
21
21
 
22
+ from simple_module_cli._app_project_manifests import (
23
+ _APP_NPM_DEPS,
24
+ _APP_NPM_DEV_DEPS,
25
+ _APP_PY_DEV_DEPS,
26
+ _FRAMEWORK_VERSION,
27
+ _db_url,
28
+ _rewrite_pyproject,
29
+ )
22
30
  from simple_module_cli._env import set_env_key
23
- from simple_module_cli.case import to_kebab_case, to_pascal_case
31
+ from simple_module_cli._local_env import (
32
+ add_postgres_password_placeholder,
33
+ comment_out_env_key,
34
+ write_local_env,
35
+ )
36
+ from simple_module_cli.case import to_kebab_case, to_pascal_case, to_snake_case
24
37
  from simple_module_cli.catalog import CATALOG, PRESETS, expand_deps
25
38
  from simple_module_cli.docker_assets import scaffold_docker_assets
26
39
  from simple_module_cli.recipes import RECIPES, ScaffoldCtx
@@ -30,7 +43,6 @@ from simple_module_cli.scaffolding import (
30
43
  create_host,
31
44
  create_module,
32
45
  create_workspace,
33
- resolve_framework_version,
34
46
  )
35
47
 
36
48
  __all__ = ["create_app_project"]
@@ -38,49 +50,6 @@ __all__ = ["create_app_project"]
38
50
  _SAMPLE_MODULE_NAME = "hello"
39
51
  _SAMPLE_MODULE_PKG = _module_to_pypi_name(_SAMPLE_MODULE_NAME)
40
52
 
41
-
42
- _FRAMEWORK_VERSION = resolve_framework_version()
43
-
44
- # Pin ``simple_module_cli`` as a dev dep so ``uv run smpy`` resolves to the
45
- # project venv. The global ``uv tool`` install runs in its own isolated venv
46
- # that can't see the project's plugin entry points (issue #134). The lint /
47
- # test tooling (ruff, ty, pytest-*) backs the generated `make lint`/`make
48
- # test` targets so a fresh app can run its own quality gates.
49
- _APP_PY_DEV_DEPS = [
50
- f"simple_module_test=={_FRAMEWORK_VERSION}",
51
- f"simple_module_cli=={_FRAMEWORK_VERSION}",
52
- "pytest>=8.0",
53
- "pytest-asyncio>=0.24",
54
- "pytest-playwright>=0.7.2",
55
- "ruff>=0.8",
56
- "ty>=0.0.29",
57
- ]
58
-
59
- _APP_NPM_DEPS = {
60
- "@simple-module-py/ui": _FRAMEWORK_VERSION,
61
- "@simple-module-py/i18n": _FRAMEWORK_VERSION,
62
- "react": "^19.0.0",
63
- "react-dom": "^19.0.0",
64
- "@inertiajs/react": "^3.7.0",
65
- }
66
- # Flat mode has no npm workspaces, so `client_app/package.json` is never
67
- # installed — the root package.json seeded from these two dicts is the only
68
- # manifest npm reads. It must therefore cover everything client_app needs to
69
- # build, not just the framework pins: `vite.config.ts` imports
70
- # `@tailwindcss/vite`, and `tsc` needs the @types packages. Keep this in step
71
- # with templates/host/client_app/package.json.tpl.
72
- _APP_NPM_DEV_DEPS = {
73
- "@simple-module-py/tsconfig": _FRAMEWORK_VERSION,
74
- "@tailwindcss/vite": "^4.0.0",
75
- "@types/node": "^26.5.0",
76
- "@types/react": "^19.0.0",
77
- "@types/react-dom": "^19.0.0",
78
- "@vitejs/plugin-react": "^6.1.1",
79
- "tailwindcss": "^4.0.0",
80
- "typescript": "^7.0.2",
81
- "vite": "^8.2.2",
82
- }
83
-
84
53
  # Files the host template ships that the workspace template re-emits at
85
54
  # the project root. Host copies are stripped in workspace mode.
86
55
  _HOST_FILES_OWNED_BY_WORKSPACE = (
@@ -150,19 +119,33 @@ def create_app_project(
150
119
  py_deps.append(_SAMPLE_MODULE_PKG)
151
120
  workspace_sources.append(_SAMPLE_MODULE_PKG)
152
121
 
153
- env_path = target / ".env.example"
154
- env_text = env_path.read_text(encoding="utf-8") if env_path.exists() else ""
155
- env_text = set_env_key(env_text, "SM_SECRET_KEY", _secrets.token_urlsafe(32))
122
+ # Generated secrets go to the gitignored .env, never the committed
123
+ # .env.example (GH #441) — see write_local_env below.
124
+ secret_keys = ["SM_SECRET_KEY"]
156
125
  if "users" in resolved:
157
126
  # UsersSettings refuses its placeholder token secrets when
158
127
  # SM_ENVIRONMENT=production — the mode the compose containers run
159
- # in — so generate real ones, same as SM_SECRET_KEY above.
160
- for key in (
128
+ # in — so generate real ones, same as SM_SECRET_KEY.
129
+ secret_keys += [
161
130
  "SM_USERS_RESET_PASSWORD_TOKEN_SECRET",
162
131
  "SM_USERS_VERIFICATION_TOKEN_SECRET",
163
- ):
164
- env_text = set_env_key(env_text, key, _secrets.token_urlsafe(32))
165
- env_text = set_env_key(env_text, "SM_DATABASE_URL", _db_url(db, to_kebab_case(name), flat=flat))
132
+ ]
133
+ db_name = to_snake_case(name)
134
+ env_path = target / ".env.example"
135
+ env_text = env_path.read_text(encoding="utf-8") if env_path.exists() else ""
136
+ for key in secret_keys:
137
+ env_text = comment_out_env_key(env_text, key)
138
+ local_overrides: dict[str, str] = {}
139
+ if db == "postgres":
140
+ # The compose `postgres` service takes its password from .env, and
141
+ # the dev SM_DATABASE_URL must match it (GH #444).
142
+ env_text = add_postgres_password_placeholder(env_text)
143
+ password = _secrets.token_urlsafe(24)
144
+ local_overrides = {
145
+ "POSTGRES_PASSWORD": password,
146
+ "SM_DATABASE_URL": _db_url(db, db_name, flat=flat, password=password),
147
+ }
148
+ env_text = set_env_key(env_text, "SM_DATABASE_URL", _db_url(db, db_name, flat=flat))
166
149
  env_text = set_env_key(env_text, "SM_MULTI_TENANT", "true" if tenancy else "false")
167
150
  env_path.write_text(env_text, encoding="utf-8")
168
151
 
@@ -198,6 +181,8 @@ def create_app_project(
198
181
  RECIPES[recipe_key].apply(target, ctx)
199
182
 
200
183
  scaffold_docker_assets(target, ctx, flat=flat)
184
+ # Last, so .env picks up every key the recipes added to .env.example.
185
+ write_local_env(target, secret_keys=secret_keys, overrides=local_overrides)
201
186
 
202
187
  return host_dir, preserved
203
188
 
@@ -232,46 +217,3 @@ def _seed_static_dist_placeholder(static_dist: Path) -> None:
232
217
  # an empty placeholder keeps `uv sync` from failing before vite build runs.
233
218
  static_dist.mkdir(parents=True, exist_ok=True)
234
219
  (static_dist / ".gitkeep").touch()
235
-
236
-
237
- def _db_url(db: str, slug: str, *, flat: bool) -> str:
238
- if db == "postgres":
239
- return f"postgresql+asyncpg://postgres:postgres@localhost:5432/{slug}"
240
- # Workspace mode keeps the SQLite file next to host/'s alembic.ini so
241
- # `cd host && uvicorn` and `cd host && alembic` resolve the same path.
242
- return "sqlite+aiosqlite:///./app.db" if flat else "sqlite+aiosqlite:///./host/app.db"
243
-
244
-
245
- def _rewrite_pyproject(
246
- text: str,
247
- deps: list[str],
248
- dev_deps: list[str],
249
- *,
250
- sources: Sequence[str] = (),
251
- project_name: str | None = None,
252
- ) -> str:
253
- """Replace deps in a host ``pyproject.toml`` and pin workspace sources.
254
-
255
- ``sources`` lists ``simple_module_*`` packages that should resolve from
256
- the uv workspace (``modules/*``) instead of PyPI. Emits a
257
- ``[tool.uv.sources]`` block per entry. Empty in flat mode.
258
-
259
- ``project_name`` overrides ``[project].name`` — set in workspace mode
260
- so the host's package name differs from the workspace root's.
261
- """
262
- import tomlkit
263
-
264
- doc = tomlkit.parse(text)
265
- project = doc.setdefault("project", tomlkit.table())
266
- if project_name is not None:
267
- project["name"] = project_name
268
- project["dependencies"] = list(deps)
269
- groups = doc.setdefault("dependency-groups", tomlkit.table())
270
- groups["dev"] = list(dev_deps)
271
- if sources:
272
- tool = doc.setdefault("tool", tomlkit.table())
273
- uv_table = tool.setdefault("uv", tomlkit.table())
274
- uv_sources = uv_table.setdefault("sources", tomlkit.table())
275
- for src in sources:
276
- uv_sources[src] = {"workspace": True}
277
- return tomlkit.dumps(doc)