Split out of docs/marketplace.md so the design document stays about how it works. Ranked by what blocks something rather than by size: submissions first (until a stranger's manifest can be read without executing it, the catalogue is ours only and everything below is cosmetic), then plugin upgrades not migrating, then staleness, then scale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.9 KiB
Marketplace — deferred work
What marketplace.officer.dev and the plugin install path do not do yet, as of 2026-08-16. Design and
rationale live in docs/marketplace.md; this is the list.
Ranked by what actually blocks something.
Blocks other people using it
-
Third-party submissions cannot use the sync.
scripts/marketplace-sync.tsreads a manifest by IMPORTING it, which is code execution. Fine for our own repositories, not a mechanism a stranger's plugin can go through. Needs either a sandbox or a publishedmanifest.jsonalongside the TypeScript one. Until this exists the catalogue is ours only, and everything below is cosmetic. -
No write API and no submission flow. Registering a plugin means running the sync by hand against the database. Deliberate while nobody can submit; the first outside contributor makes it the gap.
Blocks a plugin evolving
-
Plugin installs CREATE, they do not MIGRATE.
pushPluginSchemagenerates the SQL that builds a schema from nothing and applies it, skipping objects that already exist. A new version that adds a column will not get it — the CREATE is skipped as "already exists" and the column is silently absent. Plugin upgrades need real migrations. The old globaldb:pushappeared to handle this, at the price of being able to drop half the database, so this is a known trade rather than a regression. -
No versions table.
plugin_metadatais one row per plugin — a snapshot, not a history. A store that can offer "install 1.2 instead of 1.3", or show a changelog, needs a different shape.
Staleness
-
Nothing refreshes the catalogue automatically. Publishing a plugin update changes nothing until somebody runs
bun --env-file=.env scripts/marketplace-sync.ts. A Gitea webhook or a cron is the obvious next step. -
The sync reads local clones, not the remotes. The machine running it must have every plugin checked out, so the catalogue describes that machine's copies. The version that clones each repo into a temp directory is a small change.
-
rawUrl()assumes Gitea and amainbranch. A GitHub-hosted plugin, or one whose default branch ismaster, gets a 404 for its icon. The forge belongs in the registry rather than inferred from a URL shape.
Scale
-
fetchCatalogue()fetches everything, every time. ~737 bytes per entry, so 100 plugins ≈ 72KB and 1,000 ≈ 720KB. Correct at three; the fix at that size is search rather than pagination, and search is a server-side query that changes the shape anyway — so this is deliberately not built early. The seam isfetchCatalogue()itself, which already returns a promise with three fallback paths behind it. -
The platform fetches the whole catalogue to answer "what can I install", then discards everything already on disk. Fine at three, wasteful at 300. Moving that filter server-side means the platform telling the store what it has, which would be the first data sent TO the marketplace — worth doing deliberately rather than by accident.
-
No search, no categories, no install counts. Three plugins do not need them.
Smaller
-
A plugin's own icon is invisible before install.
assets/is published topublic/plugins/<app>/on mount, which only happens once it is installed; the catalogue'siconUrlworks around it by pointing at the repository, and a plugin author has to know to add one. -
git,pm2andsudoare still PATH lookups in the install path. They work on both machines we have, andbunxdid not — seedc420ba5. Anything new should followpushPluginSchema's pattern of resolving a path rather than trusting$PATH. -
Uninstall leaves the ecosystem file's PM2 entry removed but the process's logs behind. Harmless, unexamined.