Skip to content

sm new should scaffold host + a working sample module in a uv/npm workspace, matching the framework repo's own layout #117

Description

@antosubash

Summary

uvx --from simple_module_cli sm new my-app (0.0.7) scaffolds a flat host application: a single directory containing main.py, client_app/, migrations/, etc., depending on wheel-installed modules only. There's no modules/ directory, no sample module, and no workspace plumbing. The result is an app that can run the bundled auth/users/dashboard/permissions modules but offers no template for adding a new module — which is the framework's whole point.

The framework repo itself uses a much richer layout that's already battle-tested for module development. sm new should produce a scaled-down version of that shape.

Current sm new output

my-app/
├── .env.example
├── .gitignore
├── Makefile
├── README.md
├── alembic.ini
├── client_app/
│ ├── app.tsx
│ ├── main.tsx
│ ├── package.json # standalone, not a workspace
│ ├── pages/
│ ├── tsconfig.json
│ └── vite.config.ts
├── main.py # host entry — at the root
├── migrations/
├── package.json # weirdly empty top-level pkg.json (?)
├── pyproject.toml # only references wheel-installed modules
└── templates/

pyproject.toml lists only wheel deps, no [tool.uv.workspace]. Top-level package.json has no workspaces field. There is nowhere for a user-authored module to live, and nothing showing them how a module is wired up (Python entry-point, FE peer deps, page registration).

Framework repo's own layout (reference)

From a gh api repos/antosubash/simple_module_python/contents/... walk:

simple_module_python/
├── package.json # workspaces: ['host/client_app', 'packages/*', 'modules/*']
├── pyproject.toml # [tool.uv.workspace] members = ['framework/*', 'modules/*', 'host']
├── host/
│ ├── pyproject.toml # depends on simple_module_hosting + chosen modules via [tool.uv.sources] workspace=true
│ ├── main.py
│ ├── client_app/
│ ├── alembic.ini
│ ├── migrations/
│ ├── routes.py
│ ├── routes_i18n.py
│ ├── templates/
│ └── tests/
├── modules/
│ ├── auth/
│ │ ├── pyproject.toml # entry_points.simple_module, hatch.force-include for package.json
│ │ ├── package.json # peerDependencies: react, react-dom, @inertiajs/react, @simple-module-py/ui
│ │ ├── auth/ # the Python package
│ │ │ ├── __init__.py
│ │ │ ├── module.py # AuthModule class registered via entry_points
│ │ │ ├── contracts/
│ │ │ ├── deps.py
│ │ │ └── locales/
│ │ ├── tests/
│ │ ├── README.md
│ │ └── LICENSE
│ ├── dashboard/ # same shape
│ ├── users/
│ └── ...
├── packages/ # npm-only workspaces (UI / i18n / tsconfig)
│ ├── ui/
│ ├── i18n/
│ └── tsconfig/
└── framework/ # the framework's own Python packages
├── core/
├── db/
├── hosting/
├── cli/
└── testing/

This layout already solves several things sm new currently leaves broken or unclear:

  • uv workspace ([tool.uv.workspace] members = ['modules/*', 'host']) lets a user iterate on a custom module against the host without publishing.
  • npm workspace (workspaces: ['host/client_app', 'packages/*', 'modules/*']) puts the host's client_app/ and every module's frontend assets into a single dependency graph — vite finds module .tsx files and their JS deps without symlinks (this is the right answer to the closed Module-shipped .tsx pages need symlink + vite aliases + inline tsconfig boilerplate #73's symlink-and-alias workaround).
  • A module template to copy. The first thing anyone building on the framework wants to do is add a feature module. Today they have to reverse-engineer the wheel-installed auth//users/ packages out of .venv/site-packages/.

Proposed change

sm new my-app should produce something like:

my-app/
├── package.json # workspaces: ['host/client_app', 'modules/*']
├── pyproject.toml # [tool.uv.workspace] members = ['host', 'modules/*']
├── Makefile # delegates to host/, runs sync-js-deps + gen-pages
├── host/
│ ├── pyproject.toml # the previous root pyproject moved here
│ ├── main.py
│ ├── client_app/
│ │ ├── package.json # workspace member
│ │ ├── vite.config.ts
│ │ └── ... (existing host client app contents)
│ ├── alembic.ini
│ ├── migrations/
│ └── templates/
└── modules/
└── hello/ # ONE end-to-end example module
├── pyproject.toml # entry_points.simple_module, depends on simple_module_core
├── package.json # peerDependencies: react, @simple-module-py/ui
├── hello/
│ ├── __init__.py
│ ├── module.py # HelloModule(MetaBase): name='Hello', register_routes/menu/permissions
│ ├── routes.py # one HTTP route (e.g. GET /hello -> Inertia render)
│ └── pages/
│ └── Hello.tsx # the page rendered by the route
└── tests/
└── test_hello.py # passing pytest hitting the endpoint

The sample module should:

  • Boot cleanly when make dev is run on the freshly-scaffolded project.
  • Add a sidebar/menu entry, so the user sees their module register in the running app.
  • Demonstrate the three things a module always wires: a route, a permission, a page.
  • Use the same entry_points.simple_module mechanism the bundled modules use, so the same registration story works whether the module is workspace-local or pip-installed later.
  • Have a pyproject.toml shaped like modules/auth/pyproject.toml in the framework repo (hatchling build, [tool.hatch.build.targets.wheel.force-include] for package.json, [project.entry-points.simple_module]).

The CLI should expose flags for the workspace mode:

  • sm new my-app → workspace mode (default, scaffolds host + sample module).
  • sm new my-app --flat → today's behavior (single host, no modules dir) for users who only want to consume published modules.
  • sm create-module <name> (already present in 0.0.7) should default to writing to modules/<name>/ when run inside a workspace, instead of the current cwd.

Why this matters

This isn't just a structural nicety — several of the open frontend issues compound from the lack of a workspace:

All three problems converge on the same fix as soon as the host + modules live in one workspace.

Reference files in the framework repo

  • Top-level workspace config: pyproject.toml, package.json at repo root.
  • A canonical module: modules/auth/{pyproject.toml,package.json,auth/module.py}.
  • A canonical host: host/{pyproject.toml,main.py,client_app/}.

Mirroring those (with the bundled wheels as workspace deps via [tool.uv.sources] = { workspace = true } for the source case, or PyPI versions for the pure-consumer case) gets sm new to a layout where users have a real on-ramp to authoring modules.

Related

Environment

  • simple_module_cli 0.0.7

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions