Bundling and Uploading
How your TypeScript source becomes a row in the plugins table.
The bundler
The plugin SDK ships a thin wrapper around esbuild
(packages/plugin-sdk/tools/bundle-plugin.ts) that:
- Targets ES2020 (compatible with QuickJS and V8)
- Outputs CommonJS (
module.exports) - Bundles all imports into a single file
- Strips comments and tree-shakes
- Writes the bundle to
dist/plugin.js
You can invoke it directly:
cd packages/plugin-sdk/examples/rolling-avg-rpm
pnpm tsx ../../tools/bundle-plugin.ts
ls dist/plugin.js # → the bundle
The upload flow
A plugin row is keyed by (accountId, manifestId, version). The
upload endpoint is idempotent — uploading the same id+version
overwrites the row.
# Body: { manifest, bundleBase64 }
curl -X POST http://localhost:6161/plugins/upload \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @upload.json
Where upload.json is:
{
"manifest": { "id": "...", "version": "1.0.0", "interfaces": ["math"], "capabilities": [] },
"bundleBase64": "...base64-encoded plugin.js..."
}
The server:
- Validates the manifest against the Zod schema
- Decodes the bundle base64
- Writes the bundle to MinIO / R2 via the storage port
- Upserts the
pluginsrow, attaches the storage key, links declared capabilities
The one-liner: pnpm seed:demo-plugin
For the seeded demo plugin, there's a single command from the repo root:
pnpm seed:demo-plugin
This runs packages/plugin-sdk/scripts/bundle-and-upload.ts,
which:
- Bundles the rolling-avg-rpm example
- Reads its
plugin.json - Reads the resulting
dist/plugin.jsas base64 - Logs in as the seed user
- POSTs
{ manifest, bundleBase64 }to/plugins/upload
The script is the template — adapt it for your own plugins.
After upload
The plugin is immediately:
- Visible at
GET /plugins - Loadable by the API when an invocation hits it
- Visible in the Flutter client's Plugin Runner
(
/analysis/plugin-runner)
What you can do today
- Bundle any plugin matching the SDK's interface contracts
- Upload via
POST /plugins/uploadorpnpm seed:demo-plugin - Re-upload the same id+version to update (idempotent)
- Invoke via the Plugin Runner UI
What's coming
- Plugin source download endpoint (
GET /plugins/:id/source) — needed before the in-browser flutter_js adapter can run - Plugin signing for the marketplace (Phase 5)
- Hot-reload during authoring
- Bundle size limit + warning (today: unlimited)