Files
inkreach-teable-cli/.agents/skills/inkreach-teable/guides/development.md
T
yeuimu d3790ab856 refactor: rename skill 'inkreach-teable-ops' -> 'inkreach-teable'
Rename the skill directory and update all references (SKILL.md frontmatter
name, AGENTS.md, design doc, install.sh). Git tracks this as a rename so
history is preserved.
2026-08-05 10:35:04 +08:00

122 lines
5.3 KiB
Markdown

# Development & Deployment
How to build, restart, and debug this inkreach-teable instance. Covers the
dual-process architecture, known gotchas, and test infrastructure. For daily
operations (tables/automation), use the `inkreach-teable` CLI instead — this guide is
for changing the source code itself.
## Architecture
Two **separate production processes** managed by systemd user services. Do NOT
use `pnpm dev` as a long-running service — the embedded Next.js dev server
inside NestJS is unstable and will hang or OOM-crash.
| Service | Command | Port | Mode |
|---------|---------|------|------|
| `teable-backend.service` | `node dist/index.js` | `:3000` | `NODE_ENV=production`, `BACKEND_SKIP_NEXT_START=true` |
| `teable-frontend.service` | `next start -p 3002 -H 0.0.0.0` | `:3002` | `NODE_ENV=production` |
Users access `:3002`; the backend `:3000` serves the API only.
## Build & Restart
### Backend (no hot reload)
After editing anything under `apps/nestjs-backend/src/`:
```bash
cd apps/nestjs-backend && pnpm build # webpack, ~18s
systemctl --user restart teable-backend.service
```
`dist/` from `pnpm dev:swc` is hot-update fragments, NOT a valid artifact — always `nest build`.
### Frontend
```bash
pnpm -r -F './packages/**' build # build dependent packages first
pnpm --filter @teable/app build-fast
systemctl --user restart teable-frontend.service
```
Browser cache is aggressive — hard-refresh (Ctrl+Shift+R) after a rebuild.
### Recover from OOM crash
If `:3000` is unreachable but systemd says `active`:
```bash
systemctl --user stop teable-backend.service
pkill -9 -f 'dist/index.js'
systemctl --user start teable-backend.service
```
## Known Gotchas (from real debugging)
### Node v24 fetch drops the Authorization header
The global `fetch()` on Node v24 (undici) drops the `Authorization` request
header in certain cases (redirects, proxies). **Use `node:http`/`node:https`**
directly for any backend API call, or the `axios`-style clients. This is why
the `inkreach-teable` CLI uses `node:http` instead of `fetch`.
### Script sandbox has no `process` global
In automation script actions, `typeof process === 'undefined'`. Access secrets
via `process.env.NAME` (this is allowed — only `process.env` is exposed, not
`process` itself). Use `triggerInput._secrets`, `triggerInput._accessToken`,
`triggerInput._apiBaseUrl` for back-channel API calls. No `fs`, `eval`,
`Function`, or `child_process`.
### scheduledTime backend is a simplified implementation
The backend `scheduled-time.trigger.ts` only honors `timing.type` + `timing.interval`.
It ignores `triggerTime`, `weekdays`, `daysOfMonth`, `starting`, `ending`, `tz`.
`weeks` type is hardcoded to every Sunday. `OneTime` falls back to every 60s.
The scheduler is in-process `setInterval` — multi-instance deployments fire
duplicates. See [trigger-types.md § scheduledTime](../api-reference/trigger-types.md#6-scheduledtime--on-a-schedule).
### Webhook route mismatch
Frontend UI shows `/api/webhook/base/{baseId}/workflow/{workflowId}` but the
backend's real route is `/api/webhook/{baseId}/{workflowId}` (no `/base/` or
`/workflow/` segments). The CLI uses the real route. Webhook `authorization`
config is UI-only — backend does NOT validate it.
### Button field options capture stale isActive
A Button field's `options.workflow.isActive` is a snapshot from creation time.
After deactivating a workflow, the API validates the DB `is_active` instead —
a click returns 400 "not active". Re-`activate` the workflow, or recreate the
field. Always verify DB state, not field options.
### base_node pollution (sidebar perf)
Workflows create entries in the `base_node` table, which feeds the sidebar
navigation tree. Hundreds of test workflows → thousands of DOM nodes → jank.
The sidebar is now virtualized (`@tanstack/react-virtual`), but avoid
generating throwaway workflows; `deactivate` instead of `delete` to keep them
queryable.
### Key-name differences from official teable
The self-built automation uses `conditions` (not `filter`), `fieldIds` (not
`watchFieldIds`), category `recordCondition` (not `recordMatchesConditions`).
The `inkreach-teable` CLI handles these correctly; never use the official `teable`
CLI for automations. See [SKILL.md § Key Constraints](../SKILL.md#3-key-constraints).
## Test Infrastructure
Automation E2E tests live in `scripts/automation-test/`:
```bash
node scripts/automation-test/run-all.js # 59 cases: 8 triggers + 6 actions + cross-features
node scripts/stress-test/stress-test.js # concurrency stress test
```
Lib framework: `scripts/automation-test/lib.js`. Uses Node `http` (not fetch)
due to the auth-header bug. Test workflows are **deactivated, not deleted**
(retention policy) — the base has ~600 historical test workflows.
## Useful DB Queries
```bash
# Count workflows by active state
PGPASSWORD=teable psql -h localhost -U teable -d teable -c \
"SELECT is_active, count(*) FROM workflow WHERE deleted_time IS NULL GROUP BY is_active;"
# Find button fields and their bound workflows
PGPASSWORD=teable psql -h localhost -U teable -d teable -c \
"SELECT id, name, options->'workflow'->>'id' AS wf_id, options->'workflow'->>'isActive' AS active
FROM field WHERE type='button';"
# Recent executions
PGPASSWORD=teable psql -h localhost -U teable -d teable -c \
"SELECT id, workflow_id, status, started_at FROM workflow_execution
ORDER BY started_at DESC LIMIT 10;"
```