feat: 收录 inkreach-teable-plugin skill;install.sh 安装全部技能
skills/ 现包含 inkreach-teable + inkreach-teable-plugin, npx skills add 一并安装两个;install.sh 改为遍历安装 skills/*。
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
# Plugin development workflow
|
||||
|
||||
The standard recipe for creating, building, and deploying a dynamic React
|
||||
component plugin. Each plugin is a standalone webpack project that outputs
|
||||
an IIFE bundle.
|
||||
|
||||
## Output contract
|
||||
|
||||
Every plugin task ends with:
|
||||
1. A built IIFE bundle (`dist/main.js` + optional `dist/styles.css`)
|
||||
2. A `plugin.json` manifest
|
||||
3. Uploaded to the host via `POST /api/plugin/:id/upload-bundle`
|
||||
4. Enabled via `PUT /api/base/:baseId/plugin-config/:pluginId`
|
||||
5. Installed to the target position (the install API varies by position)
|
||||
|
||||
## Step 1 — Understand the request
|
||||
|
||||
Clarify what the user wants, then pick the position using `overview.md`.
|
||||
State the choice: "This is a ContextMenu plugin because..."
|
||||
|
||||
## Step 2 — Scaffold the plugin project
|
||||
|
||||
The `plugin-cli` lives in the repo at `packages/plugin-cli/` (not yet published
|
||||
to npm). Run it directly:
|
||||
|
||||
```bash
|
||||
cd /tmp
|
||||
node /root/deployments/inkreach-teable/packages/plugin-cli/dist/index.js create my-plugin
|
||||
cd my-plugin
|
||||
pnpm install
|
||||
```
|
||||
|
||||
This generates:
|
||||
```
|
||||
my-plugin/
|
||||
├── src/
|
||||
│ └── index.tsx # entry: calls definePlugin()
|
||||
├── plugin.json # manifest
|
||||
├── webpack.config.js # pre-configured externals
|
||||
├── tsconfig.json
|
||||
└── package.json
|
||||
```
|
||||
|
||||
If the CLI doesn't work for some reason, manually create the structure by
|
||||
copying `plugins/batch-edit-pro/` as a template.
|
||||
|
||||
## Step 3 — Write the plugin code
|
||||
|
||||
Edit `src/index.tsx`. The shape every plugin follows:
|
||||
|
||||
```typescript
|
||||
const React = window.__TEABLE_HOST__.React;
|
||||
const host = window.__TEABLE_HOST__;
|
||||
const { useFields, useTableId, useViewId } = host.SDK;
|
||||
const { Button } = host.UILib;
|
||||
const { getRecords } = host.OpenAPI;
|
||||
const { definePlugin, useSelection, getSelectionRecords } = host.PluginSDK;
|
||||
|
||||
function MyComponent() {
|
||||
const tableId = useTableId();
|
||||
const fields = useFields();
|
||||
// ... business logic
|
||||
return React.createElement('div', null, 'Hello from plugin!');
|
||||
}
|
||||
|
||||
definePlugin({
|
||||
id: 'my-plugin',
|
||||
name: 'My Plugin',
|
||||
version: '1.0.0',
|
||||
minAppVersion: '1.0.0',
|
||||
onLoad(ctx) {
|
||||
ctx.addContextMenuAction('myAction', MyComponent);
|
||||
// or: ctx.addPanel('myPanel', MyComponent);
|
||||
// or: ctx.addDashboardWidget('myWidget', MyComponent);
|
||||
// or: ctx.addView('myView', MyComponent);
|
||||
// or: ctx.addRoute('/my-page', MyComponent);
|
||||
},
|
||||
onUnload(ctx) {
|
||||
// Optional cleanup; ctx auto-disposes registered resources
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- **Never hardcode** table IDs, view IDs, or tokens — use `useTableId()`,
|
||||
`useViewId()`, and the event bus for contextual data.
|
||||
- **Use `React.createElement`** or JSX (webpack handles transpilation). The
|
||||
host's React instance is the single source of truth.
|
||||
- **Loading / error / empty states** are required. Use `useState` to manage
|
||||
async data loading and surface errors in the UI.
|
||||
- For contextMenu plugins that use `getSelectionRecords`, handle the case
|
||||
where nothing is selected (it returns an empty array).
|
||||
- Plug in to events for reactivity: `ctx.on('selection.changed', ...)`,
|
||||
`ctx.on('record.updated', ...)`.
|
||||
|
||||
### Editable `plugin.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-plugin",
|
||||
"name": "My Plugin",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "1.0.0",
|
||||
"entry": "main.js",
|
||||
"style": "styles.css",
|
||||
"positions": ["contextMenu"],
|
||||
"permissions": ["data.read", "data.write"],
|
||||
"author": "developer@example.com",
|
||||
"icon": "Pencil"
|
||||
}
|
||||
```
|
||||
|
||||
- `id` must be unique, lowercase-kebab-case.
|
||||
- `icon` is a lucide-react icon name (optional).
|
||||
- `positions` must include the position you register in `onLoad`.
|
||||
|
||||
## Step 4 — Build
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS='--max-old-space-size=2048' npx webpack --config webpack.config.js
|
||||
```
|
||||
|
||||
Or if the plugin has a build script:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
# or: npm run build
|
||||
```
|
||||
|
||||
Outputs: `dist/main.js` (IIFE) + `dist/styles.css` (if styles exist).
|
||||
|
||||
Verify the output:
|
||||
```bash
|
||||
ls -la dist/main.js dist/styles.css plugin.json
|
||||
```
|
||||
|
||||
## Step 5 — Upload the bundle
|
||||
|
||||
The upload URL includes the plugin's `id` from `plugin.json`. Extract it first,
|
||||
then upload.
|
||||
|
||||
```bash
|
||||
SESSION=$(grep auth_session /tmp/cookie_hr.txt 2>/dev/null | awk '{print $NF}')
|
||||
if [ -z "$SESSION" ]; then
|
||||
SESSION=$(curl -s -c - http://localhost:3003/api/auth/signin \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
-d '{"email":"hr_admin@xingchen.tech","password":"Xingchen@2026"}' \
|
||||
| grep auth_session | awk '{print $NF}')
|
||||
|
||||
> Account note (2026-08): `hr_admin@xingchen.tech` no longer exists on the
|
||||
> deployed instance. Create a throwaway user via `POST /api/auth/signup`
|
||||
> and add a `plugin_developer` row for it before uploading (see SKILL.md).
|
||||
fi
|
||||
|
||||
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('plugin.json'))['id'])")
|
||||
|
||||
curl -s -b "auth_session=$SESSION" \
|
||||
-F "js=@dist/main.js" \
|
||||
-F "css=@dist/styles.css" \
|
||||
-F "manifest=@plugin.json" \
|
||||
"http://localhost:3003/api/plugin/$PLUGIN_ID/upload-bundle"
|
||||
|
||||
echo "PLUGIN_ID=$PLUGIN_ID"
|
||||
```
|
||||
|
||||
- The plugin `id` in the URL must match the `id` in `plugin.json`. On first
|
||||
upload, the plugin is created; on subsequent uploads, the existing plugin is
|
||||
updated.
|
||||
- Auth supports both session cookies (`auth_session`) and Personal Access Tokens
|
||||
(`Authorization: Bearer <PAT>`).
|
||||
- Returns 201 on success.
|
||||
|
||||
## Step 6 — Enable (REQUIRED before install)
|
||||
|
||||
```bash
|
||||
curl -s -b "auth_session=$SESSION" -X PUT \
|
||||
"http://localhost:3003/api/base/bseAi6Hazmw7jPWZbfO/plugin-config/$PLUGIN_ID" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"enabled":true,"tableIds":[]}'
|
||||
```
|
||||
|
||||
`tableIds:[]` = base-wide; `tableIds:["tblX"]` = per-table. Use base-wide unless
|
||||
the user wants to restrict to specific tables.
|
||||
|
||||
## Step 7 — Install to the position
|
||||
|
||||
The install API depends on position. See `deployment.md` for the exact call.
|
||||
|
||||
Example — contextMenu on the test table:
|
||||
|
||||
```bash
|
||||
curl -s -b "auth_session=$SESSION" -X POST \
|
||||
"http://localhost:3003/api/table/tbldtYd4ZhvkcyAaada/plugin-context-menu/install" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"pluginId\":\"$PLUGIN_ID\",\"name\":\"My Plugin\"}"
|
||||
```
|
||||
|
||||
## Step 8 — Verify in the browser
|
||||
|
||||
Open the relevant page and confirm the plugin renders and works:
|
||||
- ContextMenu: right-click a record in grid view → "Plugins" group
|
||||
- Panel: bottom-right Plugins button in a table view
|
||||
- View: view-type dropdown
|
||||
- Dashboard: space dashboard page
|
||||
|
||||
Use `browser-act` to verify or ask the user to check.
|
||||
|
||||
## Pre-deliver checklist
|
||||
|
||||
Before claiming done:
|
||||
|
||||
- [ ] `dist/main.js` built and is a valid IIFE bundle
|
||||
- [ ] `plugin.json` at project root with correct `id`, `entry`, `positions`
|
||||
- [ ] No hardcoded URLs/table IDs/tokens — all via host SDK
|
||||
- [ ] Loading, error, and empty states handled in the UI
|
||||
- [ ] `definePlugin()` called with `onLoad(ctx)` that registers at least one component
|
||||
- [ ] Upload returns 201, pluginId captured
|
||||
- [ ] Enable (plugin-config) called before install
|
||||
- [ ] Install returns 2xx
|
||||
- [ ] Plugin renders and works
|
||||
|
||||
If any box is unchecked, fix it.
|
||||
Reference in New Issue
Block a user