simple-module-cli 0.0.37__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 (182) hide show
  1. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/.gitignore +1 -2
  2. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/PKG-INFO +1 -1
  3. {simple_module_cli-0.0.37 → 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.37 → simple_module_cli-0.0.38}/simple_module_cli/add_cmd.py +7 -120
  8. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/app_project.py +8 -89
  9. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/case.py +26 -0
  10. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/catalog.py +3 -7
  11. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/cli.py +7 -0
  12. simple_module_cli-0.0.38/simple_module_cli/docker_cmd.py +158 -0
  13. simple_module_cli-0.0.38/simple_module_cli/framework_repo.py +127 -0
  14. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/openapi_cmd.py +57 -33
  15. simple_module_cli-0.0.38/simple_module_cli/openapi_types.py +205 -0
  16. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/scaffolding.py +26 -1
  17. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-conventions/SKILL.md +20 -6
  18. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-creating/SKILL.md +24 -12
  19. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-doctor/SKILL.md +4 -2
  20. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-inertia-pages/SKILL.md +16 -7
  21. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-testing/SKILL.md +2 -2
  22. simple_module_cli-0.0.38/simple_module_cli/templates/docker/compose-worker.yml.tpl +12 -0
  23. simple_module_cli-0.0.38/simple_module_cli/templates/docker/worker.Dockerfile.tpl +27 -0
  24. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/app.tsx +14 -2
  25. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/templates/index.html +1 -1
  26. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/constants.py.tpl +36 -0
  27. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/contracts/__init__.py.tpl +13 -0
  28. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/contracts/schemas.py.tpl +46 -0
  29. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/deps.py.tpl +15 -0
  30. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +91 -0
  31. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/endpoints/views.py.tpl +95 -0
  32. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/locales/en.json.tpl +27 -0
  33. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/models.py.tpl +26 -0
  34. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +54 -9
  35. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Browse.tsx.tpl +57 -0
  36. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Create.tsx.tpl +43 -0
  37. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Edit.tsx.tpl +46 -0
  38. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/api.generated.ts.tpl +266 -0
  39. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/api.ts.tpl +10 -0
  40. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/py.typed +0 -0
  41. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/service.py.tpl +60 -0
  42. simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +17 -0
  43. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/LICENSE +21 -0
  44. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/README.md.tpl +34 -0
  45. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Browse.tsx.tpl +63 -0
  46. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Create.tsx.tpl +45 -0
  47. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Edit.tsx.tpl +48 -0
  48. simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/pyproject.toml.tpl +44 -0
  49. simple_module_cli-0.0.38/simple_module_cli/templates/module/tests/test___PACKAGE__.py.tpl +126 -0
  50. simple_module_cli-0.0.38/tests/test_cli_catalog_depends_on.py +49 -0
  51. simple_module_cli-0.0.38/tests/test_cli_docker_workers.py +162 -0
  52. simple_module_cli-0.0.38/tests/test_cli_gen_types_scaffold.py +65 -0
  53. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_new_regressions.py +3 -1
  54. simple_module_cli-0.0.38/tests/test_cli_openapi_gen_types.py +214 -0
  55. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_wizard.py +4 -4
  56. simple_module_cli-0.0.38/tests/test_framework_repo_wiring.py +125 -0
  57. simple_module_cli-0.0.38/tests/test_scaffold_app_tsx_spa_links.py +9 -0
  58. simple_module_cli-0.0.38/tests/test_scaffolding_framework_repo.py +140 -0
  59. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_scaffolding_module.py +1 -1
  60. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_scaffolding_module_js.py +7 -4
  61. simple_module_cli-0.0.38/tests/test_template_requires_framework.py +24 -0
  62. simple_module_cli-0.0.37/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +0 -11
  63. simple_module_cli-0.0.37/simple_module_cli/templates/module/__PACKAGE__/endpoints/views.py.tpl +0 -16
  64. simple_module_cli-0.0.37/simple_module_cli/templates/module/__PACKAGE__/pages/Index.tsx.tpl +0 -13
  65. simple_module_cli-0.0.37/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +0 -14
  66. simple_module_cli-0.0.37/simple_module_cli/templates/module/tests/test___PACKAGE__.py.tpl +0 -30
  67. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/LICENSE +0 -0
  68. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/README.md +0 -0
  69. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/__init__.py +0 -0
  70. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/_env.py +0 -0
  71. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/_local_env.py +0 -0
  72. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/_module_build.py +0 -0
  73. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/_module_host.py +0 -0
  74. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/_templating.py +0 -0
  75. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/docker_assets.py +0 -0
  76. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/git_source.py +0 -0
  77. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/module_cmd.py +0 -0
  78. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/new.py +0 -0
  79. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/package_update.py +0 -0
  80. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/pins.py +0 -0
  81. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/plugins.py +0 -0
  82. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/pypi.py +0 -0
  83. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/pyproject_edit.py +0 -0
  84. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/recipes.py +0 -0
  85. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/requirements.py +0 -0
  86. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/README.md +0 -0
  87. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-cli/SKILL.md +0 -0
  88. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-database/SKILL.md +0 -0
  89. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-locales/SKILL.md +0 -0
  90. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-migrations/SKILL.md +0 -0
  91. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-registries/SKILL.md +0 -0
  92. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/skills_cmd.py +0 -0
  93. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/Makefile.snippet +0 -0
  94. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-base-postgres.yml.tpl +0 -0
  95. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-base-sqlite.yml.tpl +0 -0
  96. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-tasks-postgres.yml.tpl +0 -0
  97. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-tasks-sqlite.yml.tpl +0 -0
  98. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/dockerignore +0 -0
  99. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/host-flat.Dockerfile +0 -0
  100. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/host.Dockerfile +0 -0
  101. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/.env.example +0 -0
  102. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/.gitignore +0 -0
  103. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/Makefile +0 -0
  104. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/README.md.tpl +0 -0
  105. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/_optional/background_tasks/Makefile.snippet +0 -0
  106. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +0 -0
  107. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/alembic.ini +0 -0
  108. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/main.tsx +0 -0
  109. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/module-assets.ts +0 -0
  110. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/package.json.tpl +0 -0
  111. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/pages/Error.tsx +0 -0
  112. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/pages/Landing.tsx +0 -0
  113. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/pages.ts +0 -0
  114. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/styles.css +0 -0
  115. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/tsconfig.json +0 -0
  116. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/vite.config.ts +0 -0
  117. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/vite.dev-url.ts +0 -0
  118. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/main.py +0 -0
  119. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/env.py +0 -0
  120. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/script.py.mako +0 -0
  121. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/versions/.gitkeep +0 -0
  122. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/pyproject.toml.tpl +0 -0
  123. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/routes.py +0 -0
  124. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.github/workflows/ci.yml +0 -0
  125. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.github/workflows/publish.yml.tpl +0 -0
  126. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.gitignore +0 -0
  127. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/README.md.tpl +0 -0
  128. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/__init__.py +0 -0
  129. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/endpoints/__init__.py +0 -0
  130. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/services.py.tpl +0 -0
  131. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/_optional/standalone/package.json.tpl +0 -0
  132. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/_optional/standalone/tsconfig.json.tpl +0 -0
  133. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/package.json.tpl +0 -0
  134. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/pyproject.toml.tpl +0 -0
  135. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/tsconfig.json.tpl +0 -0
  136. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/.env.example +0 -0
  137. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/.gitignore +0 -0
  138. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/Makefile +0 -0
  139. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/README.md.tpl +0 -0
  140. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/package.json.tpl +0 -0
  141. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/pyproject.toml.tpl +0 -0
  142. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/update_cmd.py +0 -0
  143. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/uv_workspace.py +0 -0
  144. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/simple_module_cli/wizard.py +0 -0
  145. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/conftest.py +0 -0
  146. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_build_packaging.py +0 -0
  147. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_case.py +0 -0
  148. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_add_cmd.py +0 -0
  149. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_add_spec_parse.py +0 -0
  150. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_add_workspace_member.py +0 -0
  151. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_catalog.py +0 -0
  152. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_docker_assets.py +0 -0
  153. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_git_scan.py +0 -0
  154. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_new.py +0 -0
  155. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_new_dest_tolerance.py +0 -0
  156. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_new_git_module.py +0 -0
  157. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_new_migration_bootstrap.py +0 -0
  158. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_new_scaffold_layout.py +0 -0
  159. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_openapi_export.py +0 -0
  160. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_package_update.py +0 -0
  161. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_package_update_pins.py +0 -0
  162. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_pyproject_edit.py +0 -0
  163. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_recipes.py +0 -0
  164. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_update_sources.py +0 -0
  165. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_vite_port_env.py +0 -0
  166. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_cli_wizard_git.py +0 -0
  167. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_create_module_ci_skip.py +0 -0
  168. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_env_helper.py +0 -0
  169. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_cmd_build.py +0 -0
  170. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_cmd_e2e.py +0 -0
  171. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_cmd_verify.py +0 -0
  172. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_css_assets.py +0 -0
  173. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_css_build.py +0 -0
  174. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_css_subdir_sources.py +0 -0
  175. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_npm_aliases.py +0 -0
  176. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_module_pages_manifest.py +0 -0
  177. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_no_framework_deps.py +0 -0
  178. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_plugin_discovery.py +0 -0
  179. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_scaffold_rollback.py +0 -0
  180. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_scaffolded_host_setup_wizard.py +0 -0
  181. {simple_module_cli-0.0.37 → simple_module_cli-0.0.38}/tests/test_scaffolding_host.py +0 -0
  182. {simple_module_cli-0.0.37 → 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/
@@ -65,3 +63,4 @@ host/client_app/.playwright-cli/*
65
63
  .qa/
66
64
  .verify/
67
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.37
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.37"
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)
@@ -11,15 +11,19 @@ sync-js-deps`` → entry-point verification, unless ``--no-sync``.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- import os
15
- import subprocess
16
14
  import tempfile
17
- from collections.abc import Callable
18
15
  from pathlib import Path
19
16
  from typing import Annotated
20
17
 
21
18
  import typer
22
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
23
27
  from simple_module_cli.git_source import (
24
28
  FoundModule,
25
29
  SpecError,
@@ -35,41 +39,15 @@ from simple_module_cli.pyproject_edit import (
35
39
  save_pyproject,
36
40
  write_dependency,
37
41
  write_git_source,
38
- write_path_source,
39
- write_workspace_source,
40
42
  )
41
- from simple_module_cli.uv_workspace import is_workspace_member
42
43
 
43
44
  __all__ = ["ExecRunner", "add_module", "post_install", "run_add"]
44
45
 
45
- ExecRunner = Callable[[list[str], Path], int]
46
-
47
46
  _SECURITY_NOTICE = (
48
47
  "note: this installs and runs code from a URL you chose — review the "
49
48
  "repository before booting the host."
50
49
  )
51
50
 
52
- _ENTRYPOINT_CHECK = (
53
- "import sys\n"
54
- "from importlib.metadata import distributions\n"
55
- "target = sys.argv[1].replace('-', '_').lower()\n"
56
- "for dist in distributions():\n"
57
- " if any(ep.group == 'simple_module' for ep in dist.entry_points):\n"
58
- " name = (dist.metadata['Name'] or '').replace('-', '_').lower()\n"
59
- " if name == target:\n"
60
- " sys.exit(0)\n"
61
- "sys.exit(1)\n"
62
- )
63
-
64
-
65
- def _default_exec_runner(cmd: list[str], cwd: Path) -> int:
66
- return subprocess.run(cmd, cwd=cwd, check=False).returncode
67
-
68
-
69
- def _fail(message: str) -> typer.Exit:
70
- typer.echo(f"ERROR: {message}", err=True)
71
- return typer.Exit(code=1)
72
-
73
51
 
74
52
  def _choose(
75
53
  found: list[FoundModule],
@@ -99,97 +77,6 @@ def _choose(
99
77
  ]
100
78
 
101
79
 
102
- def post_install(
103
- project_dir: Path,
104
- dists: list[str],
105
- models_dists: list[str],
106
- exec_runner: ExecRunner,
107
- ) -> None:
108
- """uv sync → gen-pages → sync-js-deps → entry-point check → reminders."""
109
- if exec_runner(["uv", "sync"], project_dir) != 0:
110
- raise _fail("`uv sync` failed — pyproject was written; fix and re-run `uv sync`.")
111
- if (project_dir / "client_app").is_dir():
112
- exec_runner(["uv", "run", "smpy", "host", "gen-pages"], project_dir)
113
- exec_runner(["uv", "run", "smpy", "host", "sync-js-deps"], project_dir)
114
- for dist in dists:
115
- code = exec_runner(["uv", "run", "python", "-c", _ENTRYPOINT_CHECK, dist], project_dir)
116
- if code != 0:
117
- raise _fail(
118
- f"{dist} installed but exposes no [project.entry-points.simple_module] "
119
- "entry point — it is not a SimpleModule module."
120
- )
121
- for dist in models_dists:
122
- typer.echo(
123
- f"{dist} ships database models — generate and apply the migration:\n"
124
- f' make migration msg="add {dist}"\n make migrate'
125
- )
126
-
127
-
128
- def _add_pypi(doc, pyproject: Path, raw: str, no_sync: bool, exec_runner: ExecRunner) -> list[str]:
129
- name = raw
130
- for i, ch in enumerate(raw):
131
- if ch in "<>=!~[; ":
132
- name = raw[:i]
133
- break
134
- constraint = raw[len(name) :]
135
- write_dependency(doc, name, constraint)
136
- save_pyproject(pyproject, doc)
137
- typer.echo(f"Added {name} (PyPI).")
138
- if not no_sync:
139
- post_install(pyproject.parent, [name], [], exec_runner)
140
- return [name]
141
-
142
-
143
- def _resolve_local_path(project_dir: Path, spec_path: Path) -> tuple[Path, str]:
144
- """Resolve a local-path spec to ``(directory, text written to pyproject)``.
145
-
146
- A relative path is resolved against the ``--path`` project (the pyproject
147
- being edited, which is what ``[tool.uv.sources]`` paths are relative to).
148
- If nothing exists there but the path exists relative to the cwd, the cwd
149
- one is used and the written path is re-expressed relative to the project.
150
- """
151
- if spec_path.is_absolute():
152
- return spec_path, str(spec_path)
153
- root = project_dir / spec_path
154
- if not root.exists() and (Path.cwd() / spec_path).exists():
155
- root = Path.cwd() / spec_path
156
- return root, os.path.relpath(root.resolve(), project_dir.resolve())
157
- return root, str(spec_path)
158
-
159
-
160
- def _add_path(
161
- doc, pyproject: Path, spec_path: Path, no_sync: bool, exec_runner: ExecRunner
162
- ) -> list[str]:
163
- root, written = _resolve_local_path(pyproject.parent, spec_path)
164
- found = [m for m in scan_modules(root) if m.subdirectory is None]
165
- if not found:
166
- raise _fail(
167
- f"{root} has no [project.entry-points.simple_module] entry point at its root "
168
- f"(relative paths resolve against the --path project, {pyproject.parent}, "
169
- "falling back to the current directory)"
170
- )
171
- mod = found[0]
172
- if is_workspace_member(pyproject.parent, root):
173
- # uv refuses a path source for a workspace member; the scaffold's own
174
- # sample module is wired the same way (unpinned + workspace = true).
175
- write_dependency(doc, mod.dist_name, "")
176
- write_workspace_source(doc, mod.dist_name)
177
- typer.echo(f"Added {mod.dist_name} (uv workspace member {written}).")
178
- else:
179
- write_dependency(doc, mod.dist_name, derive_range(mod.version))
180
- write_path_source(doc, mod.dist_name, path=written)
181
- typer.echo(f"Added {mod.dist_name} (editable path {written}).")
182
- save_pyproject(pyproject, doc)
183
- if not no_sync:
184
- post_install(
185
- pyproject.parent,
186
- [mod.dist_name],
187
- [mod.dist_name] if mod.ships_models else [],
188
- exec_runner,
189
- )
190
- return [mod.dist_name]
191
-
192
-
193
80
  def run_add(
194
81
  spec: str,
195
82
  *,
@@ -19,6 +19,14 @@ 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
31
  from simple_module_cli._local_env import (
24
32
  add_postgres_password_placeholder,
@@ -35,7 +43,6 @@ from simple_module_cli.scaffolding import (
35
43
  create_host,
36
44
  create_module,
37
45
  create_workspace,
38
- resolve_framework_version,
39
46
  )
40
47
 
41
48
  __all__ = ["create_app_project"]
@@ -43,51 +50,6 @@ __all__ = ["create_app_project"]
43
50
  _SAMPLE_MODULE_NAME = "hello"
44
51
  _SAMPLE_MODULE_PKG = _module_to_pypi_name(_SAMPLE_MODULE_NAME)
45
52
 
46
-
47
- _FRAMEWORK_VERSION = resolve_framework_version()
48
-
49
- # Pin ``simple_module_cli`` as a dev dep so ``uv run smpy`` resolves to the
50
- # project venv. The global ``uv tool`` install runs in its own isolated venv
51
- # that can't see the project's plugin entry points (issue #134). The lint /
52
- # test tooling (ruff, ty, pytest-*) backs the generated `make lint`/`make
53
- # test` targets so a fresh app can run its own quality gates.
54
- _APP_PY_DEV_DEPS = [
55
- f"simple_module_test=={_FRAMEWORK_VERSION}",
56
- f"simple_module_cli=={_FRAMEWORK_VERSION}",
57
- "pytest>=8.0",
58
- "pytest-asyncio>=0.24",
59
- "pytest-playwright>=0.7.2",
60
- # Floor: unpinned, uv backtracks playwright to 1.18 (GH #445).
61
- "playwright>=1.45",
62
- "ruff>=0.8",
63
- "ty>=0.0.29",
64
- ]
65
-
66
- _APP_NPM_DEPS = {
67
- "@simple-module-py/ui": _FRAMEWORK_VERSION,
68
- "@simple-module-py/i18n": _FRAMEWORK_VERSION,
69
- "react": "^19.0.0",
70
- "react-dom": "^19.0.0",
71
- "@inertiajs/react": "^3.7.0",
72
- }
73
- # Flat mode has no npm workspaces, so `client_app/package.json` is never
74
- # installed — the root package.json seeded from these two dicts is the only
75
- # manifest npm reads. It must therefore cover everything client_app needs to
76
- # build, not just the framework pins: `vite.config.ts` imports
77
- # `@tailwindcss/vite`, and `tsc` needs the @types packages. Keep this in step
78
- # with templates/host/client_app/package.json.tpl.
79
- _APP_NPM_DEV_DEPS = {
80
- "@simple-module-py/tsconfig": _FRAMEWORK_VERSION,
81
- "@tailwindcss/vite": "^4.0.0",
82
- "@types/node": "^26.5.0",
83
- "@types/react": "^19.0.0",
84
- "@types/react-dom": "^19.0.0",
85
- "@vitejs/plugin-react": "^6.1.1",
86
- "tailwindcss": "^4.0.0",
87
- "typescript": "^7.0.2",
88
- "vite": "^8.2.2",
89
- }
90
-
91
53
  # Files the host template ships that the workspace template re-emits at
92
54
  # the project root. Host copies are stripped in workspace mode.
93
55
  _HOST_FILES_OWNED_BY_WORKSPACE = (
@@ -255,46 +217,3 @@ def _seed_static_dist_placeholder(static_dist: Path) -> None:
255
217
  # an empty placeholder keeps `uv sync` from failing before vite build runs.
256
218
  static_dist.mkdir(parents=True, exist_ok=True)
257
219
  (static_dist / ".gitkeep").touch()
258
-
259
-
260
- def _db_url(db: str, db_name: str, *, flat: bool, password: str = "<POSTGRES_PASSWORD>") -> str:
261
- if db == "postgres":
262
- return f"postgresql+asyncpg://postgres:{password}@localhost:5432/{db_name}"
263
- # Workspace mode keeps the SQLite file next to host/'s alembic.ini so
264
- # `cd host && uvicorn` and `cd host && alembic` resolve the same path.
265
- return "sqlite+aiosqlite:///./app.db" if flat else "sqlite+aiosqlite:///./host/app.db"
266
-
267
-
268
- def _rewrite_pyproject(
269
- text: str,
270
- deps: list[str],
271
- dev_deps: list[str],
272
- *,
273
- sources: Sequence[str] = (),
274
- project_name: str | None = None,
275
- ) -> str:
276
- """Replace deps in a host ``pyproject.toml`` and pin workspace sources.
277
-
278
- ``sources`` lists ``simple_module_*`` packages that should resolve from
279
- the uv workspace (``modules/*``) instead of PyPI. Emits a
280
- ``[tool.uv.sources]`` block per entry. Empty in flat mode.
281
-
282
- ``project_name`` overrides ``[project].name`` — set in workspace mode
283
- so the host's package name differs from the workspace root's.
284
- """
285
- import tomlkit
286
-
287
- doc = tomlkit.parse(text)
288
- project = doc.setdefault("project", tomlkit.table())
289
- if project_name is not None:
290
- project["name"] = project_name
291
- project["dependencies"] = list(deps)
292
- groups = doc.setdefault("dependency-groups", tomlkit.table())
293
- groups["dev"] = list(dev_deps)
294
- if sources:
295
- tool = doc.setdefault("tool", tomlkit.table())
296
- uv_table = tool.setdefault("uv", tomlkit.table())
297
- uv_sources = uv_table.setdefault("sources", tomlkit.table())
298
- for src in sources:
299
- uv_sources[src] = {"workspace": True}
300
- return tomlkit.dumps(doc)
@@ -13,6 +13,7 @@ __all__ = [
13
13
  "InvalidScaffoldNameError",
14
14
  "to_kebab_case",
15
15
  "to_pascal_case",
16
+ "to_singular",
16
17
  "to_snake_case",
17
18
  "validate_scaffold_name",
18
19
  ]
@@ -60,6 +61,31 @@ def to_pascal_case(name: str) -> str:
60
61
  return "".join(part.capitalize() for part in snake.split("_") if part)
61
62
 
62
63
 
64
+ _UNCOUNTABLE = frozenset({"news", "status", "series", "species", "data"})
65
+ _ES_SUFFIXES = ("uses", "sses", "xes", "ches", "shes")
66
+
67
+
68
+ def to_singular(snake: str) -> str:
69
+ """Conservative singularization of a snake_case name: 'orders' -> 'order'.
70
+
71
+ Only the last ``_`` segment is inflected. Handles ``ies`` -> ``y`` and
72
+ ``uses``/``xes``/``ches``/``shes``/``sses`` -> drop ``es``; leaves ``ss``/``us``/``is``
73
+ endings and a few uncountables alone; otherwise drops one trailing ``s``.
74
+ The author renames the sample entity when this guesses wrong.
75
+ """
76
+ head, sep, word = snake.rpartition("_")
77
+ if word in _UNCOUNTABLE or not word.endswith("s") or word.endswith(("ss", "us", "is")):
78
+ return snake
79
+ # Irregular -ie plurals ("movies", "cookies") are not handled: "ies" -> "y".
80
+ if word.endswith("ies") and len(word) > 3:
81
+ word = word[:-3] + "y"
82
+ elif word.endswith(_ES_SUFFIXES):
83
+ word = word[:-2]
84
+ else:
85
+ word = word[:-1]
86
+ return head + sep + word
87
+
88
+
63
89
  _VALID_NAME_RE = re.compile(r"^[a-z][a-z0-9]*(?:[_-][a-z0-9]+)*$")
64
90
 
65
91
 
@@ -29,6 +29,7 @@ class ModuleEntry:
29
29
 
30
30
  CATALOG: dict[str, ModuleEntry] = {
31
31
  "auth": ModuleEntry("auth", "simple_module_auth", "Auth"),
32
+ # ``requires`` covers each module's ``depends_on`` (pinned by a test).
32
33
  "users": ModuleEntry("users", "simple_module_users", "Users", requires=("auth",)),
33
34
  "permissions": ModuleEntry(
34
35
  "permissions",
@@ -36,12 +37,7 @@ CATALOG: dict[str, ModuleEntry] = {
36
37
  "Permissions",
37
38
  requires=("auth", "users"),
38
39
  ),
39
- "dashboard": ModuleEntry(
40
- "dashboard",
41
- "simple_module_dashboard",
42
- "Dashboard",
43
- requires=("users",),
44
- ),
40
+ "dashboard": ModuleEntry("dashboard", "simple_module_dashboard", "Dashboard"),
45
41
  "settings": ModuleEntry("settings", "simple_module_settings", "Settings"),
46
42
  "feature_flags": ModuleEntry("feature_flags", "simple_module_feature_flags", "Feature Flags"),
47
43
  "file_storage": ModuleEntry(
@@ -54,7 +50,7 @@ CATALOG: dict[str, ModuleEntry] = {
54
50
  "background_tasks",
55
51
  "simple_module_background_tasks",
56
52
  "Background Tasks",
57
- requires=("users",),
53
+ requires=("settings",),
58
54
  recipe="background_tasks",
59
55
  ),
60
56
  "site_lock": ModuleEntry(
@@ -22,6 +22,7 @@ import typer
22
22
 
23
23
  from simple_module_cli.add_cmd import add_module, run_add
24
24
  from simple_module_cli.case import to_kebab_case
25
+ from simple_module_cli.docker_cmd import docker_app
25
26
  from simple_module_cli.module_cmd import module_app
26
27
  from simple_module_cli.new import new_project
27
28
  from simple_module_cli.openapi_cmd import openapi_app
@@ -44,6 +45,7 @@ app.command("new")(new_project)
44
45
  app.command("add")(add_module)
45
46
  app.command("update")(update_modules)
46
47
  app.command("package-update")(package_update)
48
+ app.add_typer(docker_app, name="docker")
47
49
  app.add_typer(skills_app, name="skills")
48
50
  app.add_typer(module_app, name="module")
49
51
  app.add_typer(openapi_app, name="openapi")
@@ -182,6 +184,11 @@ def create_module(
182
184
  typer.echo(" npm install")
183
185
  typer.echo(" npm run typecheck")
184
186
  typer.echo(" uv run smpy module verify")
187
+ typer.echo(
188
+ "\nThe pages ship sample API types (pages/api.generated.ts). Once the host serves "
189
+ "the module, refresh them after any endpoint change:\n"
190
+ f" uv run smpy openapi gen-types --module {package}"
191
+ )
185
192
 
186
193
 
187
194
  discover_and_mount(app)