Migration runner ready and a sister site can deploy from a clean
checkout with one .env file.
ADRs relocated (migrations/adr/ -> docs/adr/):
- migrations/ is now Alembic territory, not docs.
- All cross-references updated: CLAUDE.md, docs/PLUGIN-HOOKS.md,
docs/PLUGIN-QUICKSTART.md.
Alembic initialized (migrations/):
- env.py, script.py.mako, alembic.ini copied from Flask-Migrate
templates so `flask db migrate` and `flask db upgrade` work without
a one-time `flask db init` (which would clash with the existing
migrations/ directory).
- Baseline migration generated via autogenerate, captures all 47
tables (core models + 6 plugins) as the upgrade target. Ready for
per-site `flask db upgrade` from an empty schema.
Deploy artifacts:
- Dockerfile: python:3.12-slim base, gunicorn server, non-root user,
healthcheck against /api/auth/login. Single image bundles all six
plugins; sites enable via `flask plugin install <name>`.
- docker-compose.yml: MySQL 8 + API container, healthcheck-gated
startup, env-driven secrets that fail loud on missing values
(`${SECRET_KEY:?}` form).
- .env.example: full env-var inventory with comments. Calls out
required vs optional. Matches what ProductionConfig.validate
enforces.
docs/DEPLOY.md:
- Step-by-step per-site runbook: clone, configure .env, bring up
stack, run migrations, seed reference data, install plugins,
create admin, front with TLS, backups, updates.
- Common-issues table.
- Cross-links to ADR-004 (per-site rationale), ADR-003 (plugin
distribution), and the config source.
Skills:
- migrating-asset-schema: Alembic + one-shot data migration policy.
Rules: additive first, renames are three steps, destructive ops
need rollback, equipment migration filter per ADR-001 + ADR-005.
- hardening-flask-config: production validation, CORS allowlist
policy, JWT cookie hardening, per-site deploy isolation per ADR-004.
CLAUDE.md updated to reflect the post-Phase-5 state. No tests added
this commit; the Alembic baseline is exercised by the existing
db.create_all-based test suite (tests do not touch the migration
runner; that's by design until per-plugin migrations land).
Test count unchanged: 101 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lowers the barrier for sister sites to build their own plugins.
Generated output satisfies the framework contract out of the box.
CLI command (shopdb/plugins/cli.py):
- `flask plugin new <name> --description "..."` generates a plugin
skeleton under plugins/<name>/. Validates the name against
CONTRIBUTING.md rules (lowercase letters/digits only, no
underscores or hyphens, not in the reserved list) and refuses to
overwrite existing plugins unless --overwrite is passed.
- Output prints the next steps (install, migrate, test).
Scaffolder (shopdb/plugins/scaffolder.py):
- validate_name: enforces the naming rules
- pascal_case: lowercase-to-PascalCase for class names
- scaffold_plugin: copies templates with string.Template
substitution. Three placeholders: $name, $Name, $description.
Files with `model.py` in the path get renamed to <name>.py.
Templates (shopdb/plugins/templates/):
- manifest.json.tmpl: name, version 0.1.0, description, core_version
range >=0.1.0,<1.0.0 (broad enough to survive minor framework bumps)
- plugin.py.tmpl: <Name>Plugin class extending BasePlugin with all
required hooks implemented (meta from manifest, get_blueprint
returning the bp, get_models returning the example model). Includes
on_install hook that seeds the AssetType row.
- models/__init__.py.tmpl + models/model.py.tmpl: Asset extension
table keyed by assetid with one example field. TODO comment marks
it as a placeholder.
- api/__init__.py.tmpl + api/routes.py.tmpl: Blueprint with list and
detail endpoints using the framework's pagination + response helpers.
- schemas/__init__.py.tmpl: marshmallow schema stub.
- tests/__init__.py.tmpl + tests/test_plugin.py.tmpl: smoke tests
asserting plugin loads, get_blueprint returns Blueprint, get_models
returns at least one model.
- README.md.tmpl: one-pager for plugin authors with common edits and
next-step references.
Canary tests (tests/test_plugin_scaffold.py):
- 14 tests asserting the scaffold output passes contract checks.
- Validates name rules (lowercase, reserved, hyphens, digits, etc.)
- Verifies all expected files generated, manifest fields present.
- Loads the generated plugin via PluginLoader (spec_from_file_location
bypasses the real `plugins` package shadowing).
- Asserts subclasses BasePlugin, get_blueprint returns Blueprint,
get_models returns model with __tablename__.
- Module-scoped fixture; cleans up sys.modules + SQLAlchemy metadata
on teardown to avoid cross-test contamination.
Quickstart docs (docs/PLUGIN-QUICKSTART.md):
- 30-minute walkthrough: scaffold -> edit model -> add routes ->
install -> verify -> add hooks. Cross-links to PLUGIN-HOOKS.md and
the ADRs. Includes common-errors table.
Test count: 87 -> 101 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>