simple-module-cli 0.0.34__tar.gz → 0.0.36__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.34 → simple_module_cli-0.0.36}/PKG-INFO +1 -1
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/pyproject.toml +1 -1
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/app_project.py +15 -4
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-migrations/SKILL.md +7 -9
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/host-flat.Dockerfile +2 -2
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/host.Dockerfile +2 -2
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/app.tsx +19 -5
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/package.json.tpl +5 -5
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/pages.ts +8 -1
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/migrations/env.py +6 -3
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/migrations/script.py.mako +1 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/routes.py +1 -1
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/templates/index.html +2 -1
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/.github/workflows/ci.yml +6 -6
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/.github/workflows/publish.yml.tpl +2 -2
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/endpoints/views.py.tpl +1 -1
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/_optional/standalone/package.json.tpl +5 -3
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/package.json.tpl +1 -1
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/tests/test___PACKAGE__.py.tpl +5 -2
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_new_regressions.py +57 -3
- simple_module_cli-0.0.36/tests/test_module_css_subdir_sources.py +47 -0
- simple_module_cli-0.0.36/tests/test_scaffolded_host_setup_wizard.py +57 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_scaffolding_host.py +11 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_scaffolding_module.py +10 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_scaffolding_module_js.py +31 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/.gitignore +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/LICENSE +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/README.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/__init__.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/_env.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/_module_build.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/_module_host.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/_templating.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/add_cmd.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/case.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/catalog.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/cli.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/docker_assets.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/git_source.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/module_cmd.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/new.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/package_update.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/pins.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/plugins.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/pypi.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/pyproject_edit.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/recipes.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/requirements.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/scaffolding.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/README.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-cli/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-conventions/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-creating/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-database/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-doctor/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-inertia-pages/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-locales/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-registries/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills/simple-module-testing/SKILL.md +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/skills_cmd.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/Makefile.snippet +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/compose-base-postgres.yml.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/compose-base-sqlite.yml.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/compose-tasks-postgres.yml.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/compose-tasks-sqlite.yml.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/docker/dockerignore +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/.env.example +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/.gitignore +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/Makefile +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/README.md.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/_optional/background_tasks/Makefile.snippet +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/alembic.ini +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/main.tsx +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/module-assets.ts +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/pages/Error.tsx +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/pages/Landing.tsx +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/styles.css +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/tsconfig.json +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/vite.config.ts +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/client_app/vite.dev-url.ts +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/main.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/migrations/versions/.gitkeep +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/pyproject.toml.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/.gitignore +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/README.md.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/__init__.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/endpoints/__init__.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/pages/Index.tsx.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/services.py.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/_optional/standalone/tsconfig.json.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/pyproject.toml.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/tsconfig.json.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/workspace/.env.example +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/workspace/.gitignore +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/workspace/Makefile +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/workspace/README.md.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/workspace/package.json.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/workspace/pyproject.toml.tpl +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/update_cmd.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/wizard.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/conftest.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_build_packaging.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_case.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_add_cmd.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_add_spec_parse.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_catalog.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_docker_assets.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_git_scan.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_new.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_new_dest_tolerance.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_new_git_module.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_new_migration_bootstrap.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_new_scaffold_layout.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_package_update.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_package_update_pins.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_pyproject_edit.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_recipes.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_update_sources.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_vite_port_env.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_wizard.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_wizard_git.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_create_module_ci_skip.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_env_helper.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_module_cmd_build.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_module_cmd_e2e.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_module_cmd_verify.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_module_css_assets.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_module_css_build.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_module_npm_aliases.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_module_pages_manifest.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_no_framework_deps.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_plugin_discovery.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_scaffold_rollback.py +0 -0
- {simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/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.36
|
|
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
|
|
@@ -61,13 +61,24 @@ _APP_NPM_DEPS = {
|
|
|
61
61
|
"@simple-module-py/i18n": _FRAMEWORK_VERSION,
|
|
62
62
|
"react": "^19.0.0",
|
|
63
63
|
"react-dom": "^19.0.0",
|
|
64
|
-
"@inertiajs/react": "^
|
|
64
|
+
"@inertiajs/react": "^3.7.0",
|
|
65
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.
|
|
66
72
|
_APP_NPM_DEV_DEPS = {
|
|
67
73
|
"@simple-module-py/tsconfig": _FRAMEWORK_VERSION,
|
|
68
|
-
"@
|
|
69
|
-
"
|
|
70
|
-
"
|
|
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",
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
# Files the host template ships that the workspace template re-emits at
|
|
@@ -64,7 +64,7 @@ make migration msg="add orders module"
|
|
|
64
64
|
make migrate
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
`make migrate` runs `alembic -c host/alembic.ini upgrade heads` — note **`heads`** (plural).
|
|
67
|
+
`make migrate` runs `alembic -c host/alembic.ini upgrade heads` — note **`heads`** (plural). Histories with a branch label can end up with multiple heads, so `upgrade head` (singular) may error or under-apply.
|
|
68
68
|
|
|
69
69
|
### Branch labels — set on the FIRST revision per module
|
|
70
70
|
|
|
@@ -78,16 +78,14 @@ branch_labels = ("audit_log",) # ← add by hand on this first revision
|
|
|
78
78
|
depends_on = None
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Why:
|
|
81
|
+
Why: the label is a named target for that revision (`upgrade audit_log@head`, the module column in the Doctor). It does **not** isolate the module: autogenerate chains the revision off the current head, so history is one linear chain and `downgrade audit_log@base` walks the **entire chain beneath it, other modules included**. Never use it as a per-module rollback.
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
make downgrade # back one revision; OR, to a target:
|
|
85
|
-
uv run --project host alembic -c host/alembic.ini downgrade audit_log@base
|
|
86
|
-
```
|
|
83
|
+
To remove one module's schema:
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
- If the module's revisions are the latest in the chain, downgrade to the `down_revision` of its first revision: `uv run --project host alembic -c host/alembic.ini downgrade <that down_revision id>`.
|
|
86
|
+
- Otherwise write a dedicated migration that drops that module's tables.
|
|
89
87
|
|
|
90
|
-
Subsequent revisions for the same module **don't** need a `branch_labels` — only the first; they
|
|
88
|
+
Subsequent revisions for the same module **don't** need a `branch_labels` — only the first; they chain off it as ordinary revisions.
|
|
91
89
|
|
|
92
90
|
> Exception: the repo's bootstrap `initial_schema` revision bundles several core modules (users, permissions, settings, file_storage, feature_flags, background_tasks) into one unlabeled root migration. The per-module `branch_labels` convention applies to modules added **after** that bootstrap.
|
|
93
91
|
|
|
@@ -112,7 +110,7 @@ SM010/SM011 are **not** emitted at boot or by `make doctor`. They only surface w
|
|
|
112
110
|
|
|
113
111
|
## Pitfalls
|
|
114
112
|
|
|
115
|
-
- **
|
|
113
|
+
- **Treating `downgrade <module>@base` as a per-module rollback.** It rolls back every revision beneath the labelled one, other modules included. See the recipe under "Branch labels".
|
|
116
114
|
- **Renamed a column.** Autogenerate emits `drop_column` + `add_column`, which loses data. Edit to `op.alter_column(..., new_column_name=...)` and write the matching `downgrade()`.
|
|
117
115
|
- **Hand-edited operations with stubbed `downgrade()`.** Server defaults, CHECK constraints, expression indexes — Alembic can't always infer these. When you fill in `upgrade()`, fill in `downgrade()` too.
|
|
118
116
|
- **Concurrent `make migrate` (`upgrade heads`) from multiple processes.** The `alembic_version` table isn't race-safe across all backends. Run upgrades from one place (a release pipeline step), not from the booting app.
|
|
@@ -15,8 +15,8 @@ ENV UV_LINK_MODE=copy \
|
|
|
15
15
|
|
|
16
16
|
WORKDIR /app
|
|
17
17
|
|
|
18
|
-
# Node
|
|
19
|
-
RUN curl -fsSL https://deb.nodesource.com/
|
|
18
|
+
# Node 24 for the Vite build.
|
|
19
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
|
20
20
|
&& apt-get install -y --no-install-recommends nodejs \
|
|
21
21
|
&& rm -rf /var/lib/apt/lists/*
|
|
22
22
|
|
|
@@ -15,8 +15,8 @@ ENV UV_LINK_MODE=copy \
|
|
|
15
15
|
|
|
16
16
|
WORKDIR /app
|
|
17
17
|
|
|
18
|
-
# Node
|
|
19
|
-
RUN curl -fsSL https://deb.nodesource.com/
|
|
18
|
+
# Node 24 for the Vite build.
|
|
19
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
|
20
20
|
&& apt-get install -y --no-install-recommends nodejs \
|
|
21
21
|
&& rm -rf /var/lib/apt/lists/*
|
|
22
22
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createInertiaApp, router } from '@inertiajs/react';
|
|
2
2
|
import { configureI18n, updateI18n } from '@simple-module-py/i18n';
|
|
3
|
+
import { formatTitle, setTitleAppName } from '@simple-module-py/ui/lib/app-title';
|
|
3
4
|
import { startSpaLinkInterception } from '@simple-module-py/ui/lib/spa-links';
|
|
5
|
+
import { initTheme } from '@simple-module-py/ui/lib/theme';
|
|
4
6
|
import { createRoot } from 'react-dom/client';
|
|
5
7
|
import { resolvePage } from './pages';
|
|
6
8
|
|
|
@@ -11,22 +13,34 @@ type I18nBlock = {
|
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
createInertiaApp({
|
|
16
|
+
title: (title) => formatTitle(title),
|
|
14
17
|
resolve: async (name) => {
|
|
15
18
|
return await resolvePage(name);
|
|
16
19
|
},
|
|
17
20
|
setup({ el, App, props }) {
|
|
18
|
-
const
|
|
21
|
+
const pageProps = props.initialPage.props as {
|
|
22
|
+
i18n?: I18nBlock;
|
|
23
|
+
branding?: { appName?: string | null };
|
|
24
|
+
};
|
|
25
|
+
setTitleAppName(pageProps.branding?.appName);
|
|
26
|
+
// Before the first render, so a dark-theme user never sees a light frame.
|
|
27
|
+
initTheme();
|
|
28
|
+
const initial = pageProps.i18n;
|
|
19
29
|
configureI18n({
|
|
20
30
|
locale: initial?.locale ?? 'en',
|
|
21
31
|
messages: initial?.messages ?? {},
|
|
22
32
|
});
|
|
23
|
-
let activeLocale = initial?.locale ?? null;
|
|
24
33
|
router.on('success', (event) => {
|
|
25
34
|
const block = (event.detail.page.props as { i18n?: I18nBlock }).i18n;
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
// A non-null `messages` payload IS the server's signal that the client
|
|
36
|
+
// needs it — the backend sends `null` whenever the catalog the client
|
|
37
|
+
// already holds is still good. Gating on a locale change instead drops
|
|
38
|
+
// the catalog that arrives when the *audience* changes: signing in swaps
|
|
39
|
+
// the anonymous snapshot for one including admin-only modules, at the
|
|
40
|
+
// same locale, so every admin screen rendered raw keys
|
|
41
|
+
// ("dashboard.home.title") until a hard refresh.
|
|
42
|
+
if (block?.messages) {
|
|
28
43
|
updateI18n({ locale: block.locale, messages: block.messages });
|
|
29
|
-
activeLocale = block.locale;
|
|
30
44
|
}
|
|
31
45
|
});
|
|
32
46
|
// Authored content (pagebuilder widgets, markdown and rich-text fields)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"preview": "vite preview"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@inertiajs/react": "^
|
|
11
|
+
"@inertiajs/react": "^3.7.0",
|
|
12
12
|
"@simple-module-py/i18n": "{{FRAMEWORK_VERSION}}",
|
|
13
13
|
"@simple-module-py/ui": "{{FRAMEWORK_VERSION}}",
|
|
14
14
|
"react": "^19.0.0",
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@simple-module-py/tsconfig": "{{FRAMEWORK_VERSION}}",
|
|
19
19
|
"@tailwindcss/vite": "^4.0.0",
|
|
20
|
-
"@types/node": "^
|
|
20
|
+
"@types/node": "^26.5.0",
|
|
21
21
|
"@types/react": "^19.0.0",
|
|
22
22
|
"@types/react-dom": "^19.0.0",
|
|
23
|
-
"@vitejs/plugin-react": "^
|
|
23
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
24
24
|
"tailwindcss": "^4.0.0",
|
|
25
|
-
"typescript": "^
|
|
26
|
-
"vite": "^
|
|
25
|
+
"typescript": "^7.0.2",
|
|
26
|
+
"vite": "^8.2.2"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -15,7 +15,14 @@ import { moduleGlobs } from './modules.generated';
|
|
|
15
15
|
type PageModule = { default: React.ComponentType<Record<string, unknown>> };
|
|
16
16
|
type PageLoader = () => Promise<PageModule>;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// A `*.test.tsx` beside a page is a test, not a page — without these
|
|
19
|
+
// negations it registers as a phantom page and Vite follows the import into
|
|
20
|
+
// the production bundle.
|
|
21
|
+
const hostPages = import.meta.glob<PageModule>([
|
|
22
|
+
'./pages/**/*.tsx',
|
|
23
|
+
'!./pages/**/*.test.tsx',
|
|
24
|
+
'!./pages/**/*.spec.tsx',
|
|
25
|
+
]);
|
|
19
26
|
|
|
20
27
|
const pages: Record<string, PageLoader> = {};
|
|
21
28
|
|
|
@@ -27,12 +27,15 @@ logger = logging.getLogger("alembic.env")
|
|
|
27
27
|
config = context.config
|
|
28
28
|
|
|
29
29
|
if config.config_file_name is not None:
|
|
30
|
-
|
|
30
|
+
# Not the default disable_existing_loggers=True: the setup wizard runs this
|
|
31
|
+
# in-process, and that default would silence every app logger until restart.
|
|
32
|
+
fileConfig(config.config_file_name, disable_existing_loggers=False)
|
|
31
33
|
|
|
32
34
|
target_metadata = build_module_metadata()
|
|
33
35
|
include_object = make_include_object(target_metadata)
|
|
34
|
-
# Re-emit expression-based indexes
|
|
35
|
-
#
|
|
36
|
+
# Re-emit expression-based indexes that autogenerate drops under SQLite, rewrite
|
|
37
|
+
# Boolean 0/1 server defaults to sa.false()/sa.true(), and warn that SQLite cannot
|
|
38
|
+
# verify expression indexes (GH #342) — see make_process_revision_directives.
|
|
36
39
|
process_revision_directives = make_process_revision_directives(target_metadata)
|
|
37
40
|
|
|
38
41
|
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/routes.py
RENAMED
|
@@ -10,8 +10,8 @@ module is installed.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from fastapi import APIRouter, Request
|
|
13
|
-
from inertia import InertiaResponse
|
|
14
13
|
from simple_module_hosting.inertia_deps import InertiaDep
|
|
14
|
+
from simple_module_inertia import InertiaResponse
|
|
15
15
|
|
|
16
16
|
router = APIRouter()
|
|
17
17
|
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
crawlers and link previews see the configured site name before React
|
|
8
8
|
hydrates; Inertia's <Head> takes over per-page titles after that. #}
|
|
9
9
|
{% set brand = branding_head(request) %}
|
|
10
|
-
<title>{{ brand.app_name }}</title>
|
|
10
|
+
<title data-inertia="">{{ brand.app_name }}</title>
|
|
11
11
|
{% if brand.theme_color %}<meta name="theme-color" content="{{ brand.theme_color }}" />{% endif %}
|
|
12
|
+
{% if brand.favicon_url %}<link rel="icon" href="{{ brand.favicon_url }}" />{% endif %}
|
|
12
13
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
13
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
14
15
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
@@ -12,10 +12,10 @@ jobs:
|
|
|
12
12
|
test:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
16
|
|
|
17
17
|
- name: Install uv
|
|
18
|
-
uses: astral-sh/setup-uv@
|
|
18
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
19
19
|
with:
|
|
20
20
|
version: latest
|
|
21
21
|
|
|
@@ -34,14 +34,14 @@ jobs:
|
|
|
34
34
|
frontend:
|
|
35
35
|
runs-on: ubuntu-latest
|
|
36
36
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
37
|
+
- uses: actions/checkout@v7
|
|
38
38
|
|
|
39
|
-
- uses: actions/setup-node@
|
|
39
|
+
- uses: actions/setup-node@v7
|
|
40
40
|
with:
|
|
41
|
-
node-version: '
|
|
41
|
+
node-version: '24'
|
|
42
42
|
|
|
43
43
|
- name: Install uv
|
|
44
|
-
uses: astral-sh/setup-uv@
|
|
44
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
45
45
|
with:
|
|
46
46
|
version: latest
|
|
47
47
|
|
|
@@ -9,19 +9,21 @@
|
|
|
9
9
|
"peerDependencies": {
|
|
10
10
|
"react": "^19.0.0",
|
|
11
11
|
"react-dom": "^19.0.0",
|
|
12
|
-
"@inertiajs/react": "^
|
|
12
|
+
"@inertiajs/react": "^3.7.0",
|
|
13
13
|
"@simple-module-py/ui": "*"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@inertiajs/react": "^
|
|
16
|
+
"@inertiajs/react": "^3.7.0",
|
|
17
17
|
"@simple-module-py/i18n": "{{FRAMEWORK_VERSION}}",
|
|
18
18
|
"@simple-module-py/tsconfig": "{{FRAMEWORK_VERSION}}",
|
|
19
19
|
"@simple-module-py/ui": "{{FRAMEWORK_VERSION}}",
|
|
20
|
+
"@types/node": "^26.5.0",
|
|
20
21
|
"@types/react": "^19.0.0",
|
|
21
22
|
"@types/react-dom": "^19.0.0",
|
|
22
23
|
"react": "^19.0.0",
|
|
23
24
|
"react-dom": "^19.0.0",
|
|
24
|
-
"typescript": "^
|
|
25
|
+
"typescript": "^7.0.2",
|
|
26
|
+
"vite": "^8.2.2"
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {}
|
|
27
29
|
}
|
|
@@ -8,6 +8,7 @@ that package is installed). No conftest.py is required.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from {{PACKAGE_NAME}}.module import {{MODULE_NAME}}Module
|
|
11
|
+
from simple_module_test import effective_route_paths
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class TestMeta:
|
|
@@ -22,6 +23,8 @@ class TestRoutes:
|
|
|
22
23
|
async def test_app_boots_with_module(self, build_test_app):
|
|
23
24
|
"""Module registers cleanly into a minimal FastAPI host."""
|
|
24
25
|
app = build_test_app({{MODULE_NAME}}Module)
|
|
25
|
-
|
|
26
|
+
# effective_route_paths reads the OpenAPI schema: FastAPI >= 0.137
|
|
27
|
+
# includes routers lazily, so ``app.routes`` no longer lists them.
|
|
28
|
+
paths = effective_route_paths(app)
|
|
26
29
|
# The placeholder GET / endpoint lives under the route_prefix.
|
|
27
|
-
assert any(p
|
|
30
|
+
assert any(p.startswith("/api/{{MODULE_SLUG}}") for p in paths)
|
|
@@ -13,9 +13,9 @@ from simple_module_cli.cli import app
|
|
|
13
13
|
from typer.testing import CliRunner
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def
|
|
16
|
+
def test_sm_new_flat_pins_inertia_react_to_v3(tmp_path: Path) -> None:
|
|
17
17
|
"""Issue #128: flat scaffold's root package.json must peer-match
|
|
18
|
-
@simple-module-py/ui's ``@inertiajs/react: ^
|
|
18
|
+
@simple-module-py/ui's ``@inertiajs/react: ^3.0.0`` peer dep."""
|
|
19
19
|
runner = CliRunner()
|
|
20
20
|
target = tmp_path / "demo"
|
|
21
21
|
runner.invoke(
|
|
@@ -25,7 +25,7 @@ def test_sm_new_flat_pins_inertia_react_to_v2(tmp_path: Path) -> None:
|
|
|
25
25
|
data = json.loads((target / "package.json").read_text())
|
|
26
26
|
inertia = data.get("dependencies", {}).get("@inertiajs/react")
|
|
27
27
|
assert inertia is not None
|
|
28
|
-
assert inertia.startswith("^
|
|
28
|
+
assert inertia.startswith("^3."), f"expected ^3.x, got {inertia!r}"
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def test_sm_new_sample_module_pins_match_framework_version(tmp_path: Path) -> None:
|
|
@@ -211,6 +211,38 @@ def test_sm_new_rejects_mixed_separators(tmp_path: Path) -> None:
|
|
|
211
211
|
assert result.exit_code != 0
|
|
212
212
|
|
|
213
213
|
|
|
214
|
+
def test_scaffold_adopts_every_locale_catalog_the_server_sends(tmp_path: Path) -> None:
|
|
215
|
+
"""Issue #321: the scaffold gated ``updateI18n`` on a locale *change*, which
|
|
216
|
+
drops the catalog that arrives when the audience changes instead. Signing in
|
|
217
|
+
swaps the anonymous snapshot for one including admin-only modules at the same
|
|
218
|
+
locale, so every admin screen rendered raw keys ("dashboard.home.title")
|
|
219
|
+
until a hard refresh. A non-null ``messages`` payload is itself the server's
|
|
220
|
+
signal that the client needs it."""
|
|
221
|
+
from simple_module_cli.scaffolding import create_host
|
|
222
|
+
|
|
223
|
+
dest = tmp_path / "demo"
|
|
224
|
+
create_host(dest, name="demo-host", modules=["Dashboard", "Auth"])
|
|
225
|
+
|
|
226
|
+
app_tsx = (dest / "client_app" / "app.tsx").read_text()
|
|
227
|
+
assert "updateI18n" in app_tsx
|
|
228
|
+
assert "activeLocale" not in app_tsx, (
|
|
229
|
+
"the locale gate is back — a same-locale catalog would be dropped again"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_sm_new_host_app_tsx_wires_theme_and_title(tmp_path: Path) -> None:
|
|
234
|
+
"""Issue #429: the scaffolded app.tsx must init the theme and brand titles."""
|
|
235
|
+
from simple_module_cli.scaffolding import create_host
|
|
236
|
+
|
|
237
|
+
dest = tmp_path / "demo"
|
|
238
|
+
create_host(dest, name="demo-host", modules=["Dashboard", "Auth"])
|
|
239
|
+
|
|
240
|
+
app_tsx = (dest / "client_app" / "app.tsx").read_text()
|
|
241
|
+
assert "initTheme(" in app_tsx
|
|
242
|
+
assert "formatTitle" in app_tsx
|
|
243
|
+
assert "setTitleAppName" in app_tsx
|
|
244
|
+
|
|
245
|
+
|
|
214
246
|
def test_sm_new_no_install_next_steps_include_initial_migration(tmp_path: Path) -> None:
|
|
215
247
|
"""Issue #135: ``host/migrations/versions/`` ships empty, so a fresh
|
|
216
248
|
``make migrate`` is a no-op. The printed next-steps must therefore
|
|
@@ -224,3 +256,25 @@ def test_sm_new_no_install_next_steps_include_initial_migration(tmp_path: Path)
|
|
|
224
256
|
assert result.exit_code == 0, result.output
|
|
225
257
|
assert 'make migration msg="initial schema"' in result.output
|
|
226
258
|
assert "make migrate" in result.output
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_sm_new_flat_root_covers_client_app_build_deps(tmp_path: Path) -> None:
|
|
262
|
+
"""Flat scaffolds have no npm workspaces, so ``client_app/package.json`` is
|
|
263
|
+
never installed and the root manifest is the only one npm reads. It must
|
|
264
|
+
therefore carry everything the client build imports — ``vite.config.ts``
|
|
265
|
+
pulls in ``@tailwindcss/vite``, so a root missing it failed ``vite build``
|
|
266
|
+
with ``Could not resolve '@tailwindcss/vite'``."""
|
|
267
|
+
runner = CliRunner()
|
|
268
|
+
target = tmp_path / "demo"
|
|
269
|
+
runner.invoke(
|
|
270
|
+
app,
|
|
271
|
+
["new", "demo", "--yes", "--flat", "--no-install", "--dest", str(target)],
|
|
272
|
+
)
|
|
273
|
+
root = json.loads((target / "package.json").read_text())
|
|
274
|
+
client_app = json.loads((target / "client_app" / "package.json").read_text())
|
|
275
|
+
assert "workspaces" not in root, "flat mode must stay workspace-free"
|
|
276
|
+
|
|
277
|
+
installed = {**root.get("dependencies", {}), **root.get("devDependencies", {})}
|
|
278
|
+
required = {**client_app.get("dependencies", {}), **client_app.get("devDependencies", {})}
|
|
279
|
+
missing = sorted(set(required) - set(installed))
|
|
280
|
+
assert not missing, f"flat root package.json is missing client_app deps: {missing}"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""uv's .venv/.gitignore hides subdirectories from Tailwind, so each gets its own @source (#419)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from simple_module_hosting.assets import ModuleAssets, render_modules_css
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _wheel(tmp_path, **overrides):
|
|
9
|
+
defaults = {
|
|
10
|
+
"name": "Pagebuilder",
|
|
11
|
+
"package_name": "pagebuilder",
|
|
12
|
+
"package_dir": tmp_path / "pagebuilder",
|
|
13
|
+
"pages_dir": None,
|
|
14
|
+
"theme_css": None,
|
|
15
|
+
"styles_css": None,
|
|
16
|
+
}
|
|
17
|
+
return ModuleAssets(**{**defaults, **overrides})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_each_subdirectory_with_sources_gets_its_own_line(tmp_path):
|
|
21
|
+
components = tmp_path / "pagebuilder" / "components"
|
|
22
|
+
(components / "widgets" / "quote").mkdir(parents=True)
|
|
23
|
+
(components / "Root.tsx").write_text("")
|
|
24
|
+
(components / "widgets" / "Gallery.tsx").write_text("")
|
|
25
|
+
(components / "widgets" / "quote" / "Quote.tsx").write_text("")
|
|
26
|
+
(components / "empty").mkdir()
|
|
27
|
+
(components / "__pycache__").mkdir()
|
|
28
|
+
(components / "__pycache__" / "x.ts").write_text("")
|
|
29
|
+
|
|
30
|
+
css = render_modules_css(
|
|
31
|
+
[_wheel(tmp_path, components_dir=components)], in_repo=lambda _p: False
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
lines = [line for line in css.splitlines() if line.startswith("@source")]
|
|
35
|
+
assert lines == [
|
|
36
|
+
f'@source "{components.as_posix()}/**/*.{{ts,tsx}}";',
|
|
37
|
+
f'@source "{(components / "widgets").as_posix()}/**/*.{{ts,tsx}}";',
|
|
38
|
+
f'@source "{(components / "widgets" / "quote").as_posix()}/**/*.{{ts,tsx}}";',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_in_repo_module_subdirectories_get_nothing(tmp_path):
|
|
43
|
+
components = tmp_path / "local" / "components"
|
|
44
|
+
(components / "widgets").mkdir(parents=True)
|
|
45
|
+
(components / "widgets" / "A.tsx").write_text("")
|
|
46
|
+
css = render_modules_css([_wheel(tmp_path, components_dir=components)], in_repo=lambda _p: True)
|
|
47
|
+
assert "@source" not in css
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""A freshly scaffolded host gets a working /setup with no files of its own.
|
|
2
|
+
|
|
3
|
+
GH #351: ``SetupMiddleware`` redirected every request to ``/setup`` while the
|
|
4
|
+
route and page lived only in the framework repo's unpublished host, so a host
|
|
5
|
+
made by ``smpy create-host`` answered ``/`` → ``/setup`` → 404. The wizard now
|
|
6
|
+
ships with ``simple_module_hosting``: ``create_app`` mounts the route and
|
|
7
|
+
``gen-pages`` registers the page, so the scaffold must stay free of both.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from simple_module_hosting.manifest import write_module_pages_manifest
|
|
17
|
+
from simple_module_hosting.setup_wizard import PAGES_NAME, pages_dir
|
|
18
|
+
|
|
19
|
+
pytestmark = pytest.mark.anyio
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def test_scaffold_carries_no_setup_code_and_resolves_the_wizard(tmp_path) -> None:
|
|
23
|
+
from simple_module_cli.scaffolding import create_host
|
|
24
|
+
|
|
25
|
+
dest = tmp_path / "demo"
|
|
26
|
+
create_host(dest, name="demo-host", modules=[])
|
|
27
|
+
client_app = dest / "client_app"
|
|
28
|
+
|
|
29
|
+
# Nothing host-side: no route module, no page.
|
|
30
|
+
assert not (dest / "routes_setup.py").exists()
|
|
31
|
+
assert not list((client_app / "pages").rglob("Setup*"))
|
|
32
|
+
assert "setup" not in (dest / "main.py").read_text(encoding="utf-8").replace(
|
|
33
|
+
"setup_logging", ""
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# What `smpy gen-pages` writes for this host, with no modules installed.
|
|
37
|
+
write_module_pages_manifest([], client_app, repo_root=dest)
|
|
38
|
+
|
|
39
|
+
manifest = json.loads((client_app / "modules.manifest.json").read_text(encoding="utf-8"))
|
|
40
|
+
assert manifest[PAGES_NAME] == pages_dir().as_posix()
|
|
41
|
+
assert (pages_dir() / "Wizard.tsx").is_file()
|
|
42
|
+
|
|
43
|
+
generated = (client_app / "modules.generated.ts").read_text(encoding="utf-8")
|
|
44
|
+
assert f'"{PAGES_NAME}": import.meta.glob' in generated
|
|
45
|
+
|
|
46
|
+
# The scaffold's resolver keys a module glob entry as `<name>/<path under pages/>`,
|
|
47
|
+
# which is how `inertia.render("Setup/Wizard")` finds the page.
|
|
48
|
+
pages_ts = (client_app / "pages.ts").read_text(encoding="utf-8")
|
|
49
|
+
assert "pages[`${moduleName}/${match[1]}`]" in pages_ts
|
|
50
|
+
match = re.search(r"/pages/(.+)\.tsx$", (pages_dir() / "Wizard.tsx").as_posix())
|
|
51
|
+
assert match and f"{PAGES_NAME}/{match.group(1)}" == "Setup/Wizard"
|
|
52
|
+
|
|
53
|
+
# Tailwind must scan the wheel's wizard, and Vite must be allowed to serve it.
|
|
54
|
+
css = (client_app / "modules.generated.css").read_text(encoding="utf-8")
|
|
55
|
+
assert f'@source "{pages_dir().as_posix()}/**/*.{{ts,tsx}}";' in css
|
|
56
|
+
assets = json.loads((client_app / "modules.assets.json").read_text(encoding="utf-8"))
|
|
57
|
+
assert assets[PAGES_NAME]["pages"] == pages_dir().as_posix()
|
|
@@ -41,6 +41,17 @@ class TestCreateHost:
|
|
|
41
41
|
]:
|
|
42
42
|
assert (dest / relpath).exists(), f"missing: {relpath}"
|
|
43
43
|
|
|
44
|
+
async def test_pages_ts_excludes_colocated_test_files(self, tmp_path):
|
|
45
|
+
"""Guards template drift from host/client_app/pages.ts: without these
|
|
46
|
+
negations a colocated test file ships to the production bundle."""
|
|
47
|
+
from simple_module_cli.scaffolding import create_host
|
|
48
|
+
|
|
49
|
+
dest = tmp_path / "demo"
|
|
50
|
+
create_host(dest, name="demo-host", modules=[])
|
|
51
|
+
pages_ts = (dest / "client_app" / "pages.ts").read_text(encoding="utf-8")
|
|
52
|
+
assert "'!./pages/**/*.test.tsx'" in pages_ts
|
|
53
|
+
assert "'!./pages/**/*.spec.tsx'" in pages_ts
|
|
54
|
+
|
|
44
55
|
async def test_package_json_carries_host_name(self, tmp_path):
|
|
45
56
|
"""client_app/package.json has its `name` prefixed with the host name."""
|
|
46
57
|
from simple_module_cli.scaffolding import create_host
|
|
@@ -30,6 +30,16 @@ class TestCreateModule:
|
|
|
30
30
|
# pytest try to register `tests.conftest` as a plugin twice.
|
|
31
31
|
assert not (dest / "tests" / "__init__.py").exists()
|
|
32
32
|
|
|
33
|
+
async def test_generated_test_uses_effective_route_paths(self, tmp_path):
|
|
34
|
+
"""#431: app.routes hides lazily-included routers on FastAPI >= 0.137."""
|
|
35
|
+
from simple_module_cli.scaffolding import create_module
|
|
36
|
+
|
|
37
|
+
dest = tmp_path / "simple-module-my-feature"
|
|
38
|
+
create_module(dest, name="MyFeature")
|
|
39
|
+
text = (dest / "tests" / "test_my_feature.py").read_text()
|
|
40
|
+
assert "effective_route_paths(app)" in text
|
|
41
|
+
assert "app.routes" not in text.replace("``app.routes``", "")
|
|
42
|
+
|
|
33
43
|
async def test_test_file_basename_is_unique_per_module(self, tmp_path):
|
|
34
44
|
"""Two modules in one workspace must not ship same-named test files.
|
|
35
45
|
|
|
@@ -92,6 +92,37 @@ class TestStandaloneOverlay:
|
|
|
92
92
|
assert '"@simple-module-py/tsconfig": "0.0.27"' in pkg
|
|
93
93
|
assert '"typescript"' in pkg
|
|
94
94
|
|
|
95
|
+
async def test_standalone_ships_the_types_its_tsconfig_declares(self, tmp_path):
|
|
96
|
+
"""The shared base.json sets ``types: ["vite/client", "node"]``, so a
|
|
97
|
+
standalone module that doesn't depend on `vite` and `@types/node` fails
|
|
98
|
+
its own CI `npm run typecheck` with TS2688 "Cannot find type definition
|
|
99
|
+
file for 'node'/'vite/client'" on the very first run."""
|
|
100
|
+
import json
|
|
101
|
+
from pathlib import Path
|
|
102
|
+
|
|
103
|
+
from simple_module_cli.scaffolding import create_module
|
|
104
|
+
|
|
105
|
+
dest = tmp_path / "simple-module-my-feature"
|
|
106
|
+
create_module(dest, name="MyFeature", standalone=True)
|
|
107
|
+
|
|
108
|
+
base = json.loads(
|
|
109
|
+
(Path(__file__).parents[3] / "packages" / "tsconfig" / "base.json").read_text(
|
|
110
|
+
encoding="utf-8"
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
declared = base.get("compilerOptions", {}).get("types", [])
|
|
114
|
+
# "vite/client" is shipped by `vite`; "node" by `@types/node`.
|
|
115
|
+
providers = {"vite/client": "vite", "node": "@types/node"}
|
|
116
|
+
pkg = json.loads((dest / "package.json").read_text(encoding="utf-8"))
|
|
117
|
+
dev_deps = pkg.get("devDependencies", {})
|
|
118
|
+
for entry in declared:
|
|
119
|
+
provider = providers.get(entry)
|
|
120
|
+
assert provider is not None, f"no known provider for tsconfig type {entry!r}"
|
|
121
|
+
assert provider in dev_deps, (
|
|
122
|
+
f"base.json declares types {entry!r} but the standalone module "
|
|
123
|
+
f"scaffold does not depend on {provider!r}"
|
|
124
|
+
)
|
|
125
|
+
|
|
95
126
|
async def test_in_repo_keeps_workspace_configs(self, tmp_path):
|
|
96
127
|
from simple_module_cli.scaffolding import create_module
|
|
97
128
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/.env.example
RENAMED
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/.gitignore
RENAMED
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/Makefile
RENAMED
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/README.md.tpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/alembic.ini
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/host/main.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/module/.gitignore
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/simple_module_cli/templates/workspace/Makefile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_cli-0.0.34 → simple_module_cli-0.0.36}/tests/test_cli_new_migration_bootstrap.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|