docs(skill): plugin bundle pipeline v2 (IIFE/多资产/热更新) 文档入仓
This commit is contained in:
@@ -7,9 +7,18 @@ Uploading a bundle plugin, enabling it, and managing permissions.
|
||||
Upload the built bundle to the host. Uses `multipart/form-data`.
|
||||
The `pluginId` in the URL must match the `id` in `plugin.json`.
|
||||
|
||||
Preferred (handles entry + chunks + maps + css automatically):
|
||||
|
||||
```bash
|
||||
TEABLE_ENDPOINT=http://localhost:3003 TEABLE_PAT=<pat> \
|
||||
node /root/deployments/inkreach-teable/packages/plugin-cli/dist/index.js deploy -d .
|
||||
```
|
||||
|
||||
Raw curl equivalent (legacy single-file form still works):
|
||||
|
||||
```bash
|
||||
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('plugin.json'))['id'])")
|
||||
curl -s -b "auth_session=$SESSION" \
|
||||
curl -s -H "Authorization: Bearer $TEABLE_PAT" \
|
||||
-F "js=@dist/main.js" \
|
||||
-F "css=@dist/styles.css" \
|
||||
-F "manifest=@plugin.json" \
|
||||
@@ -17,34 +26,51 @@ curl -s -b "auth_session=$SESSION" \
|
||||
```
|
||||
|
||||
**Auth note**: Supports both session cookies (`auth_session`) and Personal Access
|
||||
Tokens (`Authorization: Bearer <PAT>`).
|
||||
Tokens (`Authorization: Bearer <PAT>`). Runtime asset GETs
|
||||
(`/api/plugin/:id/runtime/*`) are session-only (PAT gets 403 by design) —
|
||||
verify those with a signed cookie, not a PAT.
|
||||
|
||||
**Fields:**
|
||||
| Field | Required | Max size | Description |
|
||||
|---|---|---|---|
|
||||
| `js` | Yes | 5 MB | The IIFE bundle (`dist/main.js`) |
|
||||
| `css` | No | 1 MB | Optional stylesheet (`dist/styles.css`) |
|
||||
| `manifest` | Yes | 10 KB | `plugin.json` manifest |
|
||||
**Fields** (v2 multi-asset, since 2026-10):
|
||||
|
||||
**Returns** `201` with `{ id, name, version, ... }` — the plugin ID to use for
|
||||
enable and install.
|
||||
| Field | Required | Max count | Max size | Description |
|
||||
|---|---|---|---|---|
|
||||
| `js` | Yes | 20 | 5 MB each | Entry + lazy chunks. Entry = the file whose name matches `manifest.entry` (a lone differently-named js file still works — legacy fallback) |
|
||||
| `css` | No | 5 | 1 MB each | Stylesheets, concatenated under the `manifest.style` name |
|
||||
| `map` | No | 20 | 5 MB each | Source maps, served later as `application/json` |
|
||||
| `manifest` | Yes | 1 | 10 KB | `plugin.json` manifest |
|
||||
|
||||
**Registering a NEW plugin id**: `POST /api/plugin` (manifest as JSON body) —
|
||||
**admin-only** (PAT gets 403 `restricted_resource`). Uploading a bundle for an
|
||||
already-registered id only needs the `plugin_developer` whitelist. Deleting a
|
||||
plugin (`DELETE /api/plugin/:id`) is likewise admin-only. PAT-only automation
|
||||
should reuse existing registered ids, or ask an admin to register/delete.
|
||||
- **Total cap 15 MB** across all assets.
|
||||
- Chunk filenames must be **FLAT** (`x-<hash>.chunk.js`) — the multipart
|
||||
transport strips directory parts from filenames.
|
||||
- The bundle dir on disk (`<backend-cwd>/.assets/plugins/bundle/<pluginId>/`)
|
||||
is **wiped on every upload** — stale chunks are removed, and hand-patched
|
||||
files on disk are discarded.
|
||||
|
||||
**Returns** `{ pluginId, hash, size, assets }` — `assets` is the stored file
|
||||
count (1 for legacy single-file uploads).
|
||||
|
||||
**Registering a NEW plugin id**: `POST /api/plugin {"id": "<kebab-id>", ...}`
|
||||
— PAT-accessible (`@TokenAccess()` since 2026-10) and **custom ids require
|
||||
the `plugin_developer` whitelist**. Without an explicit `id` the server
|
||||
mints a `plgXXX` id, which CANNOT receive bundles (manifest id must equal
|
||||
the DB id). Deleting a plugin (`DELETE /api/plugin/:id`) remains admin-only.
|
||||
|
||||
**Security** (7-layer validation):
|
||||
1. Authentication — valid session cookie required (401 if missing)
|
||||
1. Authentication — valid session cookie or PAT required (401 if missing)
|
||||
2. Developer whitelist — `plugin_developer` table lookup (403 if not whitelisted)
|
||||
3. Rate limit — 10 uploads/min per user (429 if exceeded)
|
||||
4. Size limits — JS ≤ 5 MB, CSS ≤ 1 MB, manifest ≤ 10 KB
|
||||
5. Manifest validation — required fields, `id` regex `[a-z0-9-]+`
|
||||
6. Content validation — JS must be IIFE format (prefix `!function`/`(()=>`),
|
||||
rejects `<script>`/`eval`/`document.write` injection; CSS strips `expression()`/
|
||||
`@import`/`javascript:` URIs
|
||||
7. Integrity — SHA-256 hash stored in DB, verified at load time
|
||||
4. Size limits — JS/map ≤ 5 MB each, CSS ≤ 1 MB each, manifest ≤ 10 KB,
|
||||
15 MB total
|
||||
5. Manifest validation — required fields, `id` regex `[a-z0-9-]+`,
|
||||
`minAppVersion` ≤ host version
|
||||
6. Content validation — the ENTRY must be IIFE format (prefix `!function` /
|
||||
`(function` / `(() =>` / `var <name>=<iife>`); chunks get the
|
||||
forbidden-pattern check only (they are not IIFEs); JS rejects
|
||||
`<script>`/`document.write`/`javascript:`; CSS strips `expression()`/
|
||||
`@import`/`javascript:` URIs; maps are name/size checked only (they embed
|
||||
original source text — pattern checks would false-positive)
|
||||
7. Integrity — entry SHA-256 stored in DB for audit + PluginAuditLog row
|
||||
(uploader/IP/UA); note the loader does NOT re-verify the hash at load time
|
||||
|
||||
## Enable — `PUT /api/base/:baseId/plugin-config/:pluginId`
|
||||
|
||||
@@ -118,11 +144,28 @@ must call `ctx.addView(id, component)` to render the component as a view.
|
||||
Authority Matrix entries are auto-created for the new install (default
|
||||
denied for non-owner roles).
|
||||
|
||||
### Route
|
||||
### Route (full-page app, since 2026-10)
|
||||
|
||||
Route-type plugins registered via `ctx.addRoute(path, component)` are
|
||||
automatically available at the registered path — no install API needed.
|
||||
Upload + enable is sufficient.
|
||||
The host renders a dedicated page for the plugin at
|
||||
`/base/<baseId>/p/<pluginId>` — the whole main-area viewport inside the base
|
||||
layout. The plugin registers via `ctx.addRoute(id, component)` and typically
|
||||
brings its own router (HashRouter recommended, so Next.js routing is
|
||||
untouched).
|
||||
|
||||
```bash
|
||||
curl -s -H "Authorization: Bearer $TEABLE_PAT" -X POST \
|
||||
"http://localhost:3003/api/base/$BASE_ID/plugin-installs" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"pluginId":"$PLUGIN_ID","name":"My App","position":"route"}'
|
||||
```
|
||||
|
||||
Notes (verified 2026-10 on plugin-showcase):
|
||||
- `manifest.positions` must include `"route"`.
|
||||
- The page itself does not gate on plugin-config enablement — the URL is the
|
||||
access point. Follow the standard enable+install flow anyway so the plugin
|
||||
shows up in management UIs and future permission layers behave uniformly.
|
||||
- Host SDK hooks (e.g. `host.SDK.useBaseId()`) work on the route page because
|
||||
it renders inside BaseLayout's provider stack.
|
||||
|
||||
## Authority Matrix (plugin permissions)
|
||||
|
||||
@@ -173,23 +216,18 @@ BASE_ID="${BASE_ID:?set to a throwaway sandbox base, never a production base}"
|
||||
# Read plugin ID from manifest
|
||||
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('$DIR/plugin.json'))['id'])")
|
||||
|
||||
# Get session
|
||||
# PAT preferred (upload-bundle accepts Bearer auth):
|
||||
AUTH=(-H "Authorization: Bearer ${PAT:?set PAT env}")
|
||||
# ...or session:
|
||||
SESSION=$(grep auth_session /tmp/cookie.txt 2>/dev/null | awk '{print $NF}')
|
||||
AUTH=(-b "auth_session=$SESSION")
|
||||
|
||||
# Upload (URL includes pluginId from manifest)
|
||||
curl -s "${AUTH[@]}" \
|
||||
-F "js=@$DIR/dist/main.js" \
|
||||
-F "css=@$DIR/dist/styles.css" \
|
||||
-F "manifest=@$DIR/plugin.json" \
|
||||
"$UPLOAD_URL/api/plugin/$PLUGIN_ID/upload-bundle"
|
||||
# Upload: teable-plugin deploy handles entry + chunks + maps + css.
|
||||
# Raw-curl equivalent for a single-file bundle:
|
||||
# curl -s -H "Authorization: Bearer $TEABLE_PAT" \
|
||||
# -F "js=@$DIR/dist/main.js" -F "css=@$DIR/dist/styles.css" \
|
||||
# -F "manifest=@$DIR/plugin.json" \
|
||||
# "$UPLOAD_URL/api/plugin/$PLUGIN_ID/upload-bundle"
|
||||
TEABLE_ENDPOINT="$UPLOAD_URL" TEABLE_PAT="${TEABLE_PAT:?set PAT env}" \
|
||||
node /root/deployments/inkreach-teable/packages/plugin-cli/dist/index.js deploy -d "$DIR"
|
||||
echo "Uploaded: $PLUGIN_ID"
|
||||
|
||||
# Enable
|
||||
curl -s "${AUTH[@]}" -X PUT \
|
||||
curl -s -H "Authorization: Bearer $TEABLE_PAT" -X PUT \
|
||||
"$UPLOAD_URL/api/base/$BASE_ID/plugin-config/$PLUGIN_ID" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"enabled":true,"tableIds":[]}' > /dev/null
|
||||
|
||||
Reference in New Issue
Block a user