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.
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/.gitignore +2 -2
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/PKG-INFO +1 -1
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/pyproject.toml +1 -1
- simple_module_cli-0.0.38/simple_module_cli/_add_install.py +59 -0
- simple_module_cli-0.0.38/simple_module_cli/_add_sources.py +83 -0
- simple_module_cli-0.0.38/simple_module_cli/_app_project_manifests.py +94 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_env.py +8 -1
- simple_module_cli-0.0.38/simple_module_cli/_local_env.py +83 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/add_cmd.py +7 -89
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/app_project.py +38 -96
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/case.py +26 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/catalog.py +3 -7
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/cli.py +33 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/docker_assets.py +3 -2
- simple_module_cli-0.0.38/simple_module_cli/docker_cmd.py +158 -0
- simple_module_cli-0.0.38/simple_module_cli/framework_repo.py +127 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/new.py +1 -0
- simple_module_cli-0.0.38/simple_module_cli/openapi_cmd.py +105 -0
- simple_module_cli-0.0.38/simple_module_cli/openapi_types.py +205 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/pyproject_edit.py +19 -10
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/scaffolding.py +26 -1
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-conventions/SKILL.md +20 -6
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-creating/SKILL.md +24 -12
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-doctor/SKILL.md +6 -2
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-inertia-pages/SKILL.md +16 -7
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-testing/SKILL.md +2 -2
- simple_module_cli-0.0.38/simple_module_cli/templates/docker/Makefile.snippet +25 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-base-postgres.yml.tpl +5 -3
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-tasks-postgres.yml.tpl +3 -3
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-tasks-sqlite.yml.tpl +1 -1
- simple_module_cli-0.0.38/simple_module_cli/templates/docker/compose-worker.yml.tpl +12 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/docker/worker.Dockerfile.tpl +27 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/.env.example +3 -2
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/.gitignore +1 -3
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/Makefile +21 -2
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/README.md.tpl +1 -1
- simple_module_cli-0.0.38/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +24 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/app.tsx +14 -2
- simple_module_cli-0.0.38/simple_module_cli/templates/host/client_app/pages/Error.tsx +1 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/env.py +9 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/routes.py +1 -1
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/templates/index.html +1 -1
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/constants.py.tpl +36 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/contracts/__init__.py.tpl +13 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/contracts/schemas.py.tpl +46 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/deps.py.tpl +15 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +91 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/endpoints/views.py.tpl +95 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/locales/en.json.tpl +27 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/models.py.tpl +26 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +104 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Browse.tsx.tpl +57 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Create.tsx.tpl +43 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/Edit.tsx.tpl +46 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/api.generated.ts.tpl +266 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/pages/api.ts.tpl +10 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/py.typed +0 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/service.py.tpl +60 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +17 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/LICENSE +21 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/README.md.tpl +34 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Browse.tsx.tpl +63 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Create.tsx.tpl +45 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/__PACKAGE__/pages/Edit.tsx.tpl +48 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/_optional/framework_repo/pyproject.toml.tpl +44 -0
- simple_module_cli-0.0.38/simple_module_cli/templates/module/tests/test___PACKAGE__.py.tpl +126 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/.env.example +3 -2
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/.gitignore +1 -3
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/Makefile +19 -6
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/README.md.tpl +9 -10
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/pyproject.toml.tpl +3 -0
- simple_module_cli-0.0.38/simple_module_cli/uv_workspace.py +57 -0
- simple_module_cli-0.0.38/tests/test_cli_add_workspace_member.py +112 -0
- simple_module_cli-0.0.38/tests/test_cli_catalog_depends_on.py +49 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_docker_assets.py +82 -2
- simple_module_cli-0.0.38/tests/test_cli_docker_workers.py +162 -0
- simple_module_cli-0.0.38/tests/test_cli_gen_types_scaffold.py +65 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new.py +55 -19
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_regressions.py +3 -1
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_scaffold_layout.py +32 -0
- simple_module_cli-0.0.38/tests/test_cli_openapi_export.py +32 -0
- simple_module_cli-0.0.38/tests/test_cli_openapi_gen_types.py +214 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_recipes.py +16 -2
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_wizard.py +4 -4
- simple_module_cli-0.0.38/tests/test_framework_repo_wiring.py +125 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_css_build.py +3 -0
- simple_module_cli-0.0.38/tests/test_scaffold_app_tsx_spa_links.py +9 -0
- simple_module_cli-0.0.38/tests/test_scaffolding_framework_repo.py +140 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolding_host.py +2 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolding_module.py +1 -1
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolding_module_js.py +7 -4
- simple_module_cli-0.0.38/tests/test_template_requires_framework.py +24 -0
- simple_module_cli-0.0.36/simple_module_cli/templates/docker/Makefile.snippet +0 -13
- simple_module_cli-0.0.36/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +0 -26
- simple_module_cli-0.0.36/simple_module_cli/templates/host/client_app/pages/Error.tsx +0 -13
- simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +0 -11
- simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/endpoints/views.py.tpl +0 -16
- simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +0 -59
- simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/pages/Index.tsx.tpl +0 -13
- simple_module_cli-0.0.36/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +0 -14
- simple_module_cli-0.0.36/simple_module_cli/templates/module/tests/test___PACKAGE__.py.tpl +0 -30
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/LICENSE +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/README.md +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/__init__.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_module_build.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_module_host.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/_templating.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/git_source.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/module_cmd.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/package_update.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/pins.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/plugins.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/pypi.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/recipes.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/requirements.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/README.md +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-cli/SKILL.md +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-database/SKILL.md +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-locales/SKILL.md +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-migrations/SKILL.md +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills/simple-module-registries/SKILL.md +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/skills_cmd.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/compose-base-sqlite.yml.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/dockerignore +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/host-flat.Dockerfile +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/docker/host.Dockerfile +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/_optional/background_tasks/Makefile.snippet +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/alembic.ini +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/main.tsx +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/module-assets.ts +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/package.json.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/pages/Landing.tsx +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/pages.ts +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/styles.css +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/tsconfig.json +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/client_app/vite.config.ts +0 -0
- {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
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/main.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/script.py.mako +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/migrations/versions/.gitkeep +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/host/pyproject.toml.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.github/workflows/ci.yml +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.github/workflows/publish.yml.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/.gitignore +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/README.md.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/__init__.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/endpoints/__init__.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/__PACKAGE__/services.py.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/_optional/standalone/package.json.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/_optional/standalone/tsconfig.json.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/package.json.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/pyproject.toml.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/module/tsconfig.json.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/templates/workspace/package.json.tpl +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/update_cmd.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/simple_module_cli/wizard.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/conftest.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_build_packaging.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_case.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_add_cmd.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_add_spec_parse.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_catalog.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_git_scan.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_dest_tolerance.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_git_module.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_new_migration_bootstrap.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_package_update.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_package_update_pins.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_pyproject_edit.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_update_sources.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_vite_port_env.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_cli_wizard_git.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_create_module_ci_skip.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_env_helper.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_cmd_build.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_cmd_e2e.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_cmd_verify.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_css_assets.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_css_subdir_sources.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_npm_aliases.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_module_pages_manifest.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_no_framework_deps.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_plugin_discovery.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffold_rollback.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_scaffolded_host_setup_wizard.py +0 -0
- {simple_module_cli-0.0.36 → simple_module_cli-0.0.38}/tests/test_skills_cmd.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_cli
|
|
3
|
-
Version: 0.0.
|
|
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
|
|
@@ -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.
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
160
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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)
|