1
ADR 003 plugin distribution
Cameron Proudlock edited this page 2026-07-13 19:51:21 -04:00

ADR-003: Plugin distribution model

  • Status: ACCEPTED
  • Date: 2026-05-08
  • Deciders: cproudlock

Context

Sister sites adopting shopdb-flask need a way to:

  1. Install the framework
  2. Pick which plugins they want
  3. Build their own plugins for site-specific equipment
  4. Receive updates to both framework and plugins

Today, every plugin lives in the plugins/ directory of the framework repo. There is no separation between framework code and plugin code, no install / uninstall, and no way for a site to develop a plugin without forking the whole repo.

Three viable distribution models:

Model How a site installs a plugin
In-tree only Fork the framework repo, add plugin under plugins/, run their own deploy. No separation.
Pip-installable plugins Each plugin published as a Python package. Site does pip install shopdb-printers shopdb-network etc. Discovery via Python entry points. Framework loads any installed plugin that registers itself.
Git-based plugins Each plugin lives in its own git repo. Site clones / submodules into plugins/<name>/. Loader picks them up from the directory.

Decision

PROPOSED: Use a hybrid model with two clearly-labeled paths.

  1. Bundled plugins: a small set of plugins ships with the framework, in-tree at plugins/. These are the reference implementations and the default install (printers, computers, network, equipment, usb, notifications). A site that wants only what's bundled needs no extra work.
  2. External plugins: sister sites or third parties build plugins in their own git repos. The site running the framework drops the plugin into plugins/<name>/ (clone, submodule, or symlink) and runs flask plugin install <name>. No pip packaging required for v1.

Pip-installable plugins (Python entry-point discovery) are deferred to v2. The complexity is not justified until at least two sites are running their own plugins.

Consequences

Positive

  • v1 is simple: filesystem-based discovery (already implemented in shopdb/plugins/loader.py), works for both bundled and external plugins.
  • Sites can develop plugins without changing the framework repo.
  • The plugins/ directory is already the canonical location, so no architectural change is needed.

Negative / cost

  • No automatic update path for external plugins. Sites must git pull in each plugin directory manually. Acceptable for v1; revisit when plugin count grows.
  • Multiple plugin authors writing in parallel can collide on namespace (e.g., two plugins both registering an AssetType named "Equipment"). Need a naming policy: plugin names and asset-type names should be prefixed with the site or org if not in the bundled set.

Neutral

  • The existing in-tree pattern keeps working. This decision just formalizes it and clarifies the path for outside-the-tree plugins.

Alternatives considered

  1. Pip-installable from day one. Cleaner for the long term but adds packaging, entry-point registration, and CI steps. Premature for current scale (one site running, no sister-site plugins yet).
  2. In-tree only forever. Forces every site to fork. Doesn't scale beyond two or three sites.
  3. Submodules only. Forces git-submodule discipline on every adopting site. Submodules are notoriously fiddly. Rejected.

Open questions

  • For external plugins, should there be a manifest field (source_url) declaring where the plugin can be cloned from, so flask plugin install could pull it for the site? Defer; manual clone is fine for v1.
  • Naming convention for non-bundled plugin directory names: prefix with site? (gea-wjsf-shipping)? Adopt if and when we hit a name collision.

References

  • shopdb/plugins/loader.py (filesystem discovery)
  • shopdb/plugins/cli.py (plugin install / uninstall command)
  • ADR-001 (defines what plugins target)
  • ADR-002 (defines plugin version compatibility)