Skip to content

feat(cli): sm new wizard + standalone simple_module_cli distribution - #60

Merged
antosubash merged 24 commits into
mainfrom
feature/elastic-cray-fcd8c2
Apr 26, 2026
Merged

feat(cli): sm new wizard + standalone simple_module_cli distribution#60
antosubash merged 24 commits into
mainfrom
feature/elastic-cray-fcd8c2

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Summary

Two related improvements to the sm CLI, landing on one branch:

  1. Upgraded sm new — now scaffolds a project with any chosen subset of modules (preset or custom) and lands a runnable Celery worker + beat + Redis stack when background_tasks is selected. Wizard or flag-driven; transitive deps auto-resolved.
  2. Standalone simple_module_cli distribution — the scaffolder + Typer CLI moves out of simple_module_hosting into a new PyPI package whose only deps are typer + tomlkit. Single sm console script; plugin subcommands (sm host gen-pages, sm users create-admin, sm settings import-from-env) discovered via the simple_module_cli.cli_plugins entry-point group. All sm-* sibling scripts (sm-users, sm-settings, hosting's sm) are gone.

What changed

CLI feature work (sm new)

  • New framework/cli/simple_module_cli/{catalog,wizard,recipes,new}.py with a hardcoded module catalog, transitive expand_deps, presets (minimal / standard / full / custom), and a BackgroundTasksRecipe that drops scripts/run_worker.py, docker-compose.yml (redis + worker + beat), docker/worker.Dockerfile, Make targets, and the SM_BG_TASKS_BROKER_URL env entry.
  • create_app_project accepts a selected= module list; default standard preset preserves the existing pre-wired set.

Standalone distribution

  • New PyPI dist simple_module_cli (importable as simple_module_cli) at framework/cli/, deps: typer>=0.12, tomlkit>=0.13. No framework runtime deps; guarded by framework/cli/tests/test_no_framework_deps.py.
  • Click → Typer port across the entire CLI surface.
  • Plugin discovery: simple_module_cli.plugins.discover_and_mount walks the simple_module_cli.cli_plugins entry-point group and mounts each plugin as a named subgroup (broken/duplicate plugins log to stderr and are skipped).
  • simple_module_hosting keeps its runtime; loses cli/, scaffolding.py, app_project.py, _env.py, and templates/. Gains host_cli.py (Typer app for gen-pages + sync-js-deps).
  • users and settings modules drop sm-users / sm-settings console scripts; register Typer apps via the new entry-point group instead. settings/cli.py rewritten as a Typer app.
  • Makefile: sm gen-pagessm host gen-pages; sm sync-js-depssm host sync-js-deps.

Release plumbing

  • .github/workflows/release.yml publish-pypi matrix gains simple_module_cli. bump_version.py already auto-discovers framework/*/pyproject.toml so no script change needed.

Docs / cleanup

  • README.md, CHANGELOG.md, framework/hosting/README.md, and the new framework/cli/README.md updated for the renamed CLI distribution and the sm host/users/settings plugin shape.
  • Specs and plans saved under docs/superpowers/{specs,plans}/ for future reference.

Test plan

All gates verified locally; CI runs the same set:

  • make ci-python-lint — ruff format + check, all clean
  • make ci-python-typecheck — ty, all clean
  • make ci-check-file-size — 300-line cap respected
  • make ci-js-lint — biome (1 pre-existing warning)
  • make ci-js-typecheck — tsc per workspace
  • make test-py1017 passed, 6 deselected
  • make test-js — 8 passed (vitest)
  • make build — vite production build
  • scripts/check_metadata.py — all package metadata OK
  • scripts/check_readmes.py — all READMEs OK
  • make doctor — 0 errors (1 pre-existing SM003 warning unrelated)

End-to-end smoke:

  • uv run sm --help lists new, create-host, create-module, plus mounted plugins host, users, settings
  • uv run sm new demo --yes --preset full --no-install produces a project with scripts/run_worker.py, docker-compose.yml, docker/worker.Dockerfile, Make targets, and the broker env entry

Notes for review

  • The simple_module_hosting package no longer ships an sm console script — everything goes through the new simple_module_cli distribution. Fresh installs will need both packages for the full CLI surface (or just simple_module_cli for the scaffolder alone).
  • Plugin discovery is intentionally lenient: a broken plugin entry-point logs one line to stderr and is skipped, so sm new keeps working in degraded environments.
  • Distribution name uses the underscore form (simple_module_cli) to match simple_module_core / simple_module_hosting / etc., per the convention enforced by scripts/check_metadata.py. PyPI normalizes this to simple-module-cli on the URL side.

Designs an upgraded \`sm new\` that scaffolds a project with any chosen
subset of modules (preset or custom) and lands a runnable Celery worker
+ beat + Redis stack when background_tasks is selected — no manual
Makefile / docker-compose / run_worker.py editing required.
Implementation plan for the spec at
docs/superpowers/specs/2026-04-26-cli-modules-and-bg-jobs-design.md.

Seven TDD tasks: package split, catalog + expand_deps, wizard,
background_tasks recipe + opt-in templates, create_app_project refactor,
sm new flag wiring, lint/typecheck pass.
Preparing the file for split into a package — catalog, wizard, recipes,
new will land in dedicated modules. Console-script entry point unchanged.
Adds CATALOG, PRESETS, and expand_deps() — pure data + one pure
function. Will be wired into 'sm new' in a follow-up.
db -> tenancy -> preset (or custom checkbox loop) -> confirm. Auto-adds
required deps with a printed note. No new TUI dependency.
Recipes lay down post-scaffold artifacts (run_worker.py, docker-compose
with redis/worker/beat, worker.Dockerfile, Makefile targets, env var)
without touching framework scaffolding. Templates live under
templates/host/_optional/ and are skipped by the default copy walker.
Default 'standard' preset preserves existing behavior. Selected modules
drive both Python deps (from catalog) and post-scaffold recipes — so
selecting background_tasks now lays down a runnable Celery stack.
Scaffolds a project with any chosen subset of modules. Selecting
background_tasks lands a runnable Celery worker + beat + Redis stack
via docker compose, host Make targets, and scripts/run_worker.py — no
manual editing required.

Wizard path (sm new <name>) prompts: db -> tenancy -> preset -> confirm.
Flag path (sm new <name> --yes --preset full --with file_storage) skips
prompts. Auto-adds transitive deps with a printed note in both modes.
- Split create_app_project from scaffolding.py into app_project.py to
  stay under the 300-line cap.
- SM009 (framework -> plugin coupling) now skips templates/* paths;
  the new background_tasks run_worker.py template intentionally imports
  from the background_tasks package once it has been copied into a host
  project, where it is no longer framework code.
- ruff format + import-sort fixes, FrozenInstanceError specificity in
  test_cli_catalog.
- Move duplicated _set_env_key from app_project.py and cli/recipes.py
  into simple_module_hosting/_env.py.
- Use scaffolding._to_pascal_case for CATALOG.display normalization
  instead of ad-hoc display.replace(' ', '').
- Replace direct attribute assignment with __setattr__ in the
  frozen-dataclass test so it doesn't need ty/ruff suppressions.
Carves the scaffolder out of simple_module_hosting into a new PyPI
distribution called simple-module with deps {typer, tomlkit} only —
no framework runtime deps. Single 'sm' console script; plugin
subcommands (host gen-pages, users create-admin, etc.) discovered via
the simple_module.cli_plugins entry-point group. All sm-* sibling
scripts go away.
9-task TDD plan: bootstrap distribution, move utilities and scaffolding,
move catalog/wizard/recipes/new/app_project, port Click to Typer,
implement plugin discovery, carve sm host plugin, convert users +
settings to plugins, final dep-guard + cleanup.
New workspace member framework/cli/ shipping the simple-module PyPI
distribution. Contains stub cli.py for the sm entry point; real
commands land in subsequent commits.
Pure utilities; first pieces of the simple-module distribution. Hosting
keeps temporary workspace dep on simple-module so existing modules can
import from both during the migration.
create_host, create_module, _apply_template_files, and the entire
templates/ tree relocate from simple_module_hosting to the new
simple_module package. Hosting's scaffolding.py becomes a re-export
shim so existing import sites keep working through Task 7. Recipes
template path updated accordingly.
…ple_module

All scaffolding logic now lives in the simple_module package. Hosting's
cli/__init__.py click group still works (it imports new_project from
the new location); fully replaced by the Typer port in the next commit.
- All Click decorators rewritten in Typer's Annotated[] style.
- Wizard uses typer.prompt / typer.confirm.
- Tests use typer.testing.CliRunner (drop-in for click.testing).
- simple_module_hosting drops sm/simple-module console scripts.
- simple_module_hosting/cli/ package deleted.
- Plugin discovery hook added (no-op stub; real impl next commit).
discover_and_mount() walks the entry-point group, validates each load
target is a typer.Typer, and mounts it under its entry name. Broken or
duplicate plugins log one line to stderr and are skipped.
gen-pages and sync-js-deps move out of the deleted sm console script
and into a Typer plugin published under the simple_module.cli_plugins
entry-point group. Makefile updated for the new sm host * shape.
Drops sm-users and sm-settings console scripts. Both modules now
register Typer apps under the simple_module.cli_plugins entry-point
group, mounted as \`sm users\` and \`sm settings\`. settings/cli.py
rewritten as a Typer app (was hand-rolled Click-style argv parsing).
README updated for the new command shape; pytest testpaths gain
framework/cli/tests.
- Delete simple_module_hosting.scaffolding shim and the workspace
  dep on simple_module.
- Add framework/cli/tests/test_no_framework_deps.py to guard against
  future dep drift in the standalone scaffolder.
- Use enum.StrEnum (Python 3.12+) for Db / Preset.
- README + Makefile reflect the final sm host/users/settings shape.
- scripts/new_module.py uses simple_module.case.to_pascal_case
  instead of its private to_class_name helper.
- app_project.py hoists previously-deferred catalog/recipes/scaffolding
  imports to module scope; the import cycle they were breaking no
  longer exists after the carve-out.
- new_project drops the db_value/tenancy_value shadow vars; the two
  branches now assign the final tuple directly.
The PyPI distribution name 'simple-module' is already taken. Rename to
'simple-module-cli' (and importable package 'simple_module_cli').

- framework/cli/simple_module/ → framework/cli/simple_module_cli/
- All Python imports: from simple_module.X → from simple_module_cli.X
- Entry-point group simple_module.cli_plugins → simple_module_cli.cli_plugins
  (plugin registrations in hosting / users / settings updated)
- Console scripts sm / simple-module now point at simple_module_cli.cli:main
- Templates package: simple_module.templates → simple_module_cli.templates
- Release workflow: simple_module_cli added to publish-pypi matrix
- Dep-guard test pinned to distribution('simple-module-cli')
- README + CHANGELOG + hosting README refer to the new name
scripts/check_metadata.py enforces 'simple_module_*' names across the
framework. Switch the new distribution from kebab 'simple-module-cli'
to underscore 'simple_module_cli' to match every other framework
package (simple_module_core, simple_module_hosting, etc.).

- pyproject.toml: name = 'simple_module_cli'
- README + CHANGELOG snippets use the underscore form for install
  instructions (PyPI normalizes either way; we keep the convention
  consistent with the rest of the repo)
- test_no_framework_deps pins distribution('simple_module_cli')
- framework/cli/README.md gains explicit Install / Usage sections
  (required by scripts/check_readmes.py)
@antosubash
antosubash merged commit 1a7e639 into main Apr 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant