diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b491466..f77fc98f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to this project are documented in this file. The format is b ### Fixed +- The `simple-module-creating` and `simple-module-conventions` skills, which ship inside `simple_module_cli`, linked the module layout standard as `../../docs/module-layout.md`. That path does not exist in an installed CLI. They now link https://docs.py.simplemodule.dev/module-layout, and a test fails if a shipped skill links a file outside `skills/`. `docs/module-layout.md` gains a "Downstream modules" section on copying this repo's root-layout test (`ALLOWED_ROOT`, `DOMAIN_AREAS`) into an app or a standalone module repo (#875). - `smpy new` warns when the CLI running it is older than the latest `simple_module_cli` on PyPI. A new app pins the framework to the CLI's own version, and `uvx --from simple_module_cli` quietly reuses a copy installed with `uv tool install`, so an old tool used to scaffold an outdated app without saying so. The warning names both fixes: `uv tool upgrade simple_module_cli`, or `uvx --from simple_module_cli@latest`. The check is best effort, with a 3-second timeout; offline or on any PyPI error it is skipped, and it never blocks the scaffold. A CLI that is itself a pre-release, dev or local build is never warned. The README, the CLI and hosting READMEs and the landing page's copy command now use `uvx --from simple_module_cli@latest`, and the installation and quickstart guides, the docs home and the make-commands reference say how to upgrade an installed CLI, with `pipx upgrade` as the alternative (#899). - The first-module guidance now agrees with itself. The guides (`docs/index.md`, quickstart, installation, Your first module) wire a new module with `smpy add ../modules/orders --path host`, the command `smpy create-module` prints, which adds it to `host/pyproject.toml` as a workspace source. They used to say `uv add ./modules/orders`, which in a `smpy new` workspace edits the root `pyproject.toml` instead. A flat app uses `smpy add ./modules/orders`. The landing page `smpy new` generates drops the `create-admin` step (the `/setup` wizard already creates the first administrator), lists the wiring and migration steps after `create-module`, and its "Read the docs" button opens the first-module guide on docs.py.simplemodule.dev instead of the GitHub repo. The first-module guide and the `simple-module-conventions` skill note that SQLModel's `Field(regex=...)` is accepted but not enforced on a DTO, and show a `field_validator` instead (#898). - A fresh app's `make dev` no longer prints Vite errors or warnings, and `npm audit` reports nothing. The `simple_module_settings` wheel no longer ships `ui/components/validation-errors.test.ts`: that test made the host's Vite dependency scan fail with "Failed to run dependency scan … vitest". It now lives in `modules/settings/tests-js/`, and `make ci-build-packages` fails if any wheel contains a JS or TS `*.test.*` or `*.spec.*` file, a `test-dom.tsx` or a `page-tests/` directory. The scaffold's `client_app/vite.config.ts` loads under Vite 8's native config loader: it uses `import.meta.dirname` and `.ts` import extensions, and the template `tsconfig.json` sets `allowImportingTsExtensions`. It also uses `optimizeDeps.rolldownOptions` instead of the deprecated `esbuildOptions`. New apps' root `package.json` overrides `postcss-selector-parser` to `^6.1.5`, which is what `@tailwindcss/typography` pulls in, as this repo does. An existing app can copy `host/client_app/vite.config.ts` and `tsconfig.json` from a new app, and add the override (#900). diff --git a/docs/module-layout.md b/docs/module-layout.md index 58eb845d..3970052e 100644 --- a/docs/module-layout.md +++ b/docs/module-layout.md @@ -171,6 +171,25 @@ root-layout test too. A shim that is a plain re-export instead (not an alias) mu by hand to the expiry test's `_SHIM_MODULES` tuple and `_EXPECTED` set and to the root-layout test's `DEPRECATED_ROOT` map until removed. +## Downstream modules + +A module created with `smpy create-module` already follows this layout. The +check that keeps it that way lives in this repo's +[`host/tests/test_module_layout.py`](https://github.com/antosubash/simple_module_python/blob/main/host/tests/test_module_layout.py), +which an app or a standalone module repo does not have. To keep the same ratchet: + +1. Copy that test into your own tests, for example `tests/test_module_layout.py`. +2. Point `MODULES` at your modules directory (`modules/` in a `smpy new` app). The + copy computes it with `parents[2]`, which is right for `host/tests/`; from + `tests/test_module_layout.py` use `parents[1]`. + A standalone module repo has the package at its root, with no `modules//` + level, so change the `package`, `pyproject.toml` and `tests/` paths in the test too. +3. Replace `DOMAIN_AREAS` with your own modules' named areas, and empty + `DEPRECATED_ROOT` unless you keep root-level shims. + +`ALLOWED_ROOT` is the root lookup list in [Package root](#package-root-lookup-paths); +keep the two in step when you upgrade the framework. + ## Moving an existing module One module per PR; no logic changes in a move PR. diff --git a/docs/superpowers/specs/2026-11-11-skill-links-design.md b/docs/superpowers/specs/2026-11-11-skill-links-design.md new file mode 100644 index 00000000..aaa5a66f --- /dev/null +++ b/docs/superpowers/specs/2026-11-11-skill-links-design.md @@ -0,0 +1,16 @@ +# Shipped skills link to files the wheel contains (#875) + +`skills/` is installed as `simple_module_cli/skills/` (a symlink in the repo). Two skills +linked `../../docs/module-layout.md`, which points outside the installed package. + +## Decisions + +- Link the docs site, `https://docs.py.simplemodule.dev/module-layout` (returns 200), + instead of shipping `docs/` in the wheel. The site follows `main` and needs no new + packaging. A GitHub link at a tag would need a version stamp. +- Add `framework/cli/tests/test_cli_skill_links.py`: every relative link (inline, + titled, angle-bracket or reference-style, outside code fences) in any Markdown file + under `skills/` must resolve inside `skills/` and exist. It fails on `main` and passes now. +- Add a "Downstream modules" section to `docs/module-layout.md`: copy + `host/tests/test_module_layout.py`, point `MODULES` at the app's modules, and set your + own `DOMAIN_AREAS`. The issue asked for this. diff --git a/framework/cli/tests/test_cli_skill_links.py b/framework/cli/tests/test_cli_skill_links.py new file mode 100644 index 00000000..32621775 --- /dev/null +++ b/framework/cli/tests/test_cli_skill_links.py @@ -0,0 +1,32 @@ +"""Shipped skills link only to files the CLI wheel contains (GH #875). + +`skills/` is installed as `simple_module_cli/skills/`, so a relative link that +leaves it (`../../docs/...`) points nowhere in a downstream app. Repo docs are +linked through the docs site instead. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +_SKILLS = Path(__file__).resolve().parents[3] / "skills" +_LINK = re.compile(r"\]\(]+)>?(?:\s+\"[^\"]*\")?\)|^\s*\[[^\]]+\]:\s*?\s*$", re.M) + + +def _relative_links(skill_md: Path) -> list[str]: + text = re.sub(r"^(```|~~~).*?^\1", "", skill_md.read_text(), flags=re.M | re.S) + targets = [(m.group(1) or m.group(2)).split("#", 1)[0] for m in _LINK.finditer(text)] + return [t for t in targets if t and not re.match(r"[a-z][a-z0-9+.-]*:", t, re.I)] + + +@pytest.mark.parametrize( + "skill_md", sorted(_SKILLS.glob("**/*.md")), ids=lambda p: str(p.relative_to(_SKILLS)) +) +def test_relative_links_stay_inside_the_shipped_skills(skill_md: Path) -> None: + for target in _relative_links(skill_md): + resolved = (skill_md.parent / target).resolve() + assert resolved.is_relative_to(_SKILLS.resolve()), f"{target} leaves skills/" + assert resolved.exists(), f"{target} does not exist" diff --git a/skills/simple-module-conventions/SKILL.md b/skills/simple-module-conventions/SKILL.md index 783798a3..bd4c632c 100644 --- a/skills/simple-module-conventions/SKILL.md +++ b/skills/simple-module-conventions/SKILL.md @@ -41,7 +41,7 @@ Treat 300 lines as the design pressure on every `.py`, `.ts`, and `.tsx` file (t New module settings and their app-state container live in `core/settings.py` and `core/services.py`; HTTP dependencies and services live in `web/`. Keep root lookup files in place and put non-page frontend code in `ui/`. See -[Module layout](../../docs/module-layout.md). +[Module layout](https://docs.py.simplemodule.dev/module-layout). ### 3. Per-module settings: `register_module_settings` → `app.state.` diff --git a/skills/simple-module-creating/SKILL.md b/skills/simple-module-creating/SKILL.md index 289bad1a..3537d0c1 100644 --- a/skills/simple-module-creating/SKILL.md +++ b/skills/simple-module-creating/SKILL.md @@ -66,7 +66,7 @@ Modules use this area layout. Area initializers hold only a docstring. Private helpers stay in their area. Tests have no `__init__.py`, keep `conftest.py` at their root and use globally unique `test_orders_*.py` basenames; JS tests stay in `tests-js/`. -See [Module layout](../../docs/module-layout.md) for the lookup consumers and shim policy. +See [Module layout](https://docs.py.simplemodule.dev/module-layout) for the lookup consumers and shim policy. ## The contract: pyproject.toml + module.py