Commit Graph

3 Commits

Author SHA1 Message Date
cproudlock
529b9f2fed Ship plugin framework shore-up: frontend scaffold, sister-site adoption kit
All checks were successful
CI / backend (push) Successful in 24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
- flask plugin new now scaffolds the frontend too: List/Detail/Form
  views on the global styles, a gated route module (ADR-009), and an
  api-client snippet emitted into the plugin dir. Views are written
  before the route file so a partially generated plugin cannot 500 the
  dev server.
- docs/PLUGIN-EXTERNAL-REPO.md + scripts/test-external-plugin.sh: how a
  sister site develops a plugin in its own repo and runs the framework
  contract tests in CI against a pinned framework ref (script verified
  to fail on a broken core_version pin).
- docs/CONTRACT-STABILITY.md: settled vs churning contract surface and
  the provisional 1.0 criteria.
- CLAUDE.md active-state refresh (contract 0.6.0, 11 plugins, 340
  tests, measuringtools done).

Known limitation documented: Path.rglob does not descend symlinks, so
the import-surface contract test skips symlinked external plugins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:30:03 -04:00
cproudlock
f663cc5bbe Enforce plugin contract purity: single import surface via shopdb.api
Plugins were reaching into internal core paths (shopdb.core.models.*,
shopdb.extensions, shopdb.utils.*), coupling them to core's file layout and
violating the ADR-001 contract. Consolidate onto one versioned surface.

- shopdb.api: expand from 2 helpers to the full plugin import surface -
  db, cache; BaseModel, AuditMixin; core models (Asset, AssetType,
  AssetStatus, Vendor, Model, Communication, CommunicationType, Location,
  Setting, AuditLog, Application, AppVersion, OperatingSystem); response +
  pagination helpers; employee_connection. Documented in PLUGIN-HOOKS.md.
- Migrate all 22 plugin source files to import only from shopdb.api (plus
  shopdb.plugins.base for the ABC).
- Drop the printers plugin's legacy MachineType dependency: remove
  _ensure_legacy_machine_types and the seed_supplies machinetypeid lookup
  (Model.machinetypeid is nullable; printers carry type via PrinterType).
- Guard test test_plugins_only_import_contract_surface scans plugin source
  and fails on any core import outside shopdb.api / shopdb.plugins.base.
- Scaffold templates updated so generated plugins are contract-pure.
- Bump __contract_version__ 0.2.0 -> 0.3.0 (additive surface expansion;
  manifests pin <1.0.0 so they still satisfy).

145 tests pass, naming/style green, app factory boots all 6 plugins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:45:06 -04:00
cproudlock
5de1fbe8a8 Phase 4: plugin scaffolding (flask plugin new) with canary tests
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>
2026-05-08 17:13:46 -04:00