Skip to content

Prepare 0.0.1 public release: PyPI + npm packaging, release workflow, sm new CLI - #49

Merged
antosubash merged 62 commits into
mainfrom
feature/zen-goldberg-0ea4d2
Apr 21, 2026
Merged

antosubash merged 62 commits into
mainfrom
feature/zen-goldberg-0ea4d2

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Summary

Makes simple_module_python releasable to PyPI (14 packages) and npm (3 packages) at version 0.0.1, with a manual-dispatch GitHub Actions workflow, a simple-module new CLI generator for greenfield apps, and lockstep version bumping.

Executes docs/superpowers/plans/2026-04-21-public-release.md end-to-end across 9 phases.

What changed

Packaging (Phases 0–5)

  • LICENSE (MIT) + CHANGELOG.md seeded with 0.0.1
  • All 14 Python packages renamed to simple_module_* (underscored) with full metadata: keywords, classifiers, URLs, hatchling build targets
  • 3 npm packages published under @simple-module-py/* scope with publishConfig.access=public
  • Substantive READMEs on every one of the 17 packages
  • CI validators: scripts/check_metadata.py + scripts/check_readmes.py wired into make lint

Tooling (Phases 6–7)

  • scripts/bump_version.py — lockstep bumper for all 17 packages (uses tomlkit to preserve formatting), with --check and --dry-run
  • simple-module new my-app / sm new my-app — greenfield app generator wrapping the existing host scaffolder, pre-wires users + dashboard + permissions, generates a SM_SECRET_KEY, picks a DB URL, pins framework versions in pyproject.toml + package.json. Invokable via uvx --from simple_module_hosting simple-module new …
  • framework/hosting/tests/test_cli_new.py — 5 new tests cover the CLI end-to-end

Release infra (Phase 8)

  • .github/workflows/release.yml — manual-dispatch, OIDC Trusted Publishing (no tokens), matrix fan-out over 14 PyPI + 3 npm packages, with TestPyPI rehearsal path and a post-publish smoke test
  • docs/release.md — operator playbook: Trusted Publisher setup, GitHub Environments, partial-publish recovery
  • Root README.md — new "Use in a new project" section

Verification (Phase 9)

  • Local rehearsal green: simple-module newuv build --all-packages emits 14 wheels + 14 sdists → npm pack emits 3 tarballs
  • 26 new tests all pass (metadata, readme, bump_version, cli_new validators)
  • scripts/bump_version.py 0.0.1 --check reports "All 17 packages at 0.0.1."

Reviewer notes

  • Name convention: Python dist names use underscores (simple_module_core), npm stays hyphenated (@simple-module-py/ui), CLI commands stay hyphenated (simple-module). PyPI normalizes both forms at resolution, but metadata, workflow matrices, and docs all use the underscored form for consistency with what uv build emits.
  • Trusted Publishing: The workflow assumes PyPI/npm Trusted Publisher entries are configured per docs/release.md. None of these credentials live in the repo or in CI secrets.
  • Branch protection: The release workflow pushes a version-bump commit to main. If branch protection blocks github-actions[bot], set RELEASE_PUSH_TOKEN (fine-grained PAT, contents:write) — the workflow falls back to it.
  • Pre-existing test drift (not fixed): 3 tests in scripts/tests/test_generated.py + test_scaffold.py expect the module scaffolder to emit contracts/service.py but it now emits contracts/schemas.py. Predates this PR; flagged as a separate task.
  • Open items before running the workflow: confirm PyPI account owner, npm @simple-module-py org owner, and repo URL (all assumed antosubash / simple_module_python throughout).

Test plan

  • `make lint` — `check_metadata.py` + `check_readmes.py` both green
  • `uv run pytest scripts/tests/ framework/hosting/tests/test_cli_new.py` — 26/26 pass
  • `uv run simple-module new smoke-app --yes --db sqlite --no-install` — scaffolds with correct pinned deps and a real SECRET_KEY
  • `uv run python scripts/bump_version.py 0.0.1 --check` — reports all 17 at 0.0.1
  • `uv build --all-packages` — 14 wheels + 14 sdists
  • `npm pack` on each `packages/*/` — 3 tarballs
  • Dispatch workflow on `testpypi` with `0.0.1a0` — operator step per docs/release.md
  • Dispatch workflow on `pypi` with `0.0.1` — operator step

Captures the decisions for publishing all 14 simple-module-*
packages to PyPI: lockstep versioning from 0.0.1, manual-dispatch
GitHub Actions workflow with PyPI Trusted Publishing, sm new CLI
generator, per-package READMEs as a first-class deliverable, and
TestPyPI rehearsal before real publication.
Extends the v0.0.1 release plan to publish the three JS packages
(@simple-module/ui, i18n, tsconfig) to npm alongside the 14 Python
packages. Lockstep versioning across all 17. Generator consumes
npm packages as deps instead of vendoring. Adds npm Trusted
Publisher setup, npm matrix publish jobs, cross-registry error
handling, and npm pack smoke tests.

Renamed: pypi-release-design.md -> public-release-design.md.
…yword

- npm packages publish under @simple-module-py/ scope (was
  @simple-module/). Scope name communicates "Python framework's
  JS companion packages" more clearly.
- Every one of the 17 packages MUST declare "simple-module" as a
  keyword/tag so the whole family is discoverable via a single
  search on both PyPI and npm.
- Add scripts/check_metadata.py to lint: enforces the keyword,
  real description, license, repository URL, and publishConfig
  on every package.
Renames 10 module packages from bare names (auth, users, products, ...)
to simple-module-<name> so they can be published to PyPI without name
collisions. Python import names are unaffected — only the PyPI
distribution name changes. Inter-module dependency references and
[tool.uv.sources] keys updated to match.

Also adds [tool.hatch.build.targets.wheel] packages to each module so
hatchling can still find the source directory after the distribution
name no longer matches the package directory layout, and updates
host/pyproject.toml's dependencies and workspace sources.
PEP 503 treats hyphens and underscores as equivalent in distribution
names, but the canonical display form can differ. This repo's Python
import names are all underscored (simple_module_core, etc.), so the
PyPI distribution names are aligned to match — simple-module-core
becomes simple_module_core, and so on for all 14 packages.

npm scope (@simple-module-py/*) is unchanged.
CLI shell names (sm, simple-module) are unchanged.
The 'simple-module' PyPI keyword is unchanged (it's a search tag).
Scaffolds a fresh app pre-wired with users, dashboard, and
permissions. Generates a random SM_SECRET_KEY, sets the DB URL
based on --db choice, pins simple_module_* deps to 0.0.1, and
lists @simple-module-py/* npm deps. Respects --yes for
non-interactive invocation and --no-install to skip uv/npm/alembic.
- README: `uvx simple-module` would fail because no PyPI package is
  named `simple-module` — the CLI ships inside `simple_module_hosting`.
  Use `uvx --from simple_module_hosting simple-module new …`.
- release.yml smoke step: use underscored dist name for consistency
  with the rest of the workflow and the wheel metadata.
Resolves three conflicts with main's db-backed-settings feature (PR #47)
and datasets view-routing fix (PR #48):

- modules/settings/pyproject.toml: kept both new blocks added on either
  side — [project.scripts] sm-settings entry (from main) AND [project.urls]
  metadata (from this branch).
- modules/settings/settings/pages/Modules.tsx: accepted main's deletion
  (replaced by ModulesEdit.tsx under the new admin UI). Our only edit
  here was the mechanical @simple-module → @simple-module-py scope
  rename, which no longer applies to a deleted file.
- modules/settings/settings/pages/ModulesEdit.tsx (new in main): updated
  its @simple-module/* imports to @simple-module-py/* to stay consistent
  with the npm scope rename on this branch.

Post-merge validators green:
  - scripts/check_metadata.py: All package metadata OK.
  - scripts/check_readmes.py: All READMEs OK.
  - scripts/bump_version.py 0.0.1 --check: All 17 packages at 0.0.1.
- framework/hosting/tests/test_cli_new.py: apply ruff format + organize
  imports + replace `[... for ... in ...][0]` with `next(...)` (RUF015).
- packages/{ui,i18n,tsconfig}/package.json: re-apply biome's preferred
  multiline formatting for `files` and `publishConfig` arrays/objects.
- pyproject.toml: add `scripts` to [tool.ty.environment].extra-paths so
  ty can resolve bare imports like `from new_module import …` used by
  the pre-existing `scripts/tests/*` suite. (`scripts/__init__.py` was
  added in Phase 2 which made ty start treating scripts/ as a package;
  extra-paths is the idiomatic fix, matching every other module entry.)
The module scaffolder used to emit \`contracts/service.py\` defining an
\`IOrderService(Protocol)\`; it now emits only \`contracts/schemas.py\` with
DTOs, keeping the concrete service implementation at the package root.
These three test assertions predated that change and were failing CI.

- test_scaffold.py::test_scaffold_creates_all_files — drop
  \`contracts/service.py\` from expected files.
- test_generated.py::test_contracts_protocol_defined — remove; the
  Protocol file is no longer generated.
- test_generated.py::test_contracts_init_exports_public_api — drop the
  \`"IOrderService"\` assertion; only DTO names are re-exported now.
…ugh and troubleshooting

Keeps the existing reference material but fronts it with a TL;DR, a
step-by-step first-publish walkthrough, a local pre-flight script, a
troubleshooting section, and a table of what's published where. Aimed
at someone who's never dispatched a GitHub Actions release workflow
before, while staying useful as a reference once setup is done.
@antosubash
antosubash merged commit 41403ed into main Apr 21, 2026
10 checks passed
antosubash added a commit that referenced this pull request Apr 22, 2026
The README and .env.example tell users to set the first-boot admin
credentials in .env, but the documented path silently did nothing:
UsersSettings deliberately has no `env_file` (runtime fields come from
the DB), and the `os.environ.get(...)` fallback doesn't see `.env`-only
vars. Result: fresh `.env` + `make dev` created no admin, and logins
failed with no clue why.

- Add `simple_module_core.dotenv.parse_dotenv()` and swap the nearly
  identical parser already inlined in `simple_module_core.__main__`
  over to it.
- In `users.bootstrap`, read the four `SM_USERS_BOOTSTRAP_*` keys from
  `.env` as a final fallback after settings → os.environ. Centralize
  the setting-attr → env-var mapping in `BOOTSTRAP_ENV_KEYS` so the
  test fixture and the resolver stay in sync.
- Add commented-out bootstrap hints to `.env.example` and the
  `sm new`-scaffolded template so the knob is discoverable again
  (PR #49 trimmed it out).
- Isolate users/tests/test_bootstrap.py from the developer's real
  `.env` via an autouse fixture.
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