- skill: SKILL.md (7 actions in description + routing table entry), action-types.md (new section #4 sendNotification: config keys, variables, system-in-app semantics), automation-guide.md/cli-reference.md (--to-user/--title/--message/--severity/--path), development.md (7 actions), metadata.json abstract - cli: action add --type sendNotification now builds config from granular flags (toUserId/title/message/severity/path); ACTION_TYPES includes sendNotification; live-verified via workflow create + action add + delete
144 lines
6.7 KiB
Markdown
144 lines
6.7 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` | `:3003` | `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 `:3003` 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 `:3003` 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)
|
|
|
|
### Webpack cache may not rewrite `dist/index.js` after a workspace package rebuild
|
|
After rebuilding a workspace package (e.g. `@teable/openapi` dist), `pnpm build` in
|
|
`apps/nestjs-backend` can report "compiled successfully" while leaving `dist/index.js`
|
|
untouched (node_modules paths are treated as immutable cache inputs). Symptom: backend
|
|
behavior unchanged after a code change. Fix: `rm apps/nestjs-backend/dist/index.js`
|
|
then rebuild, and verify with `grep '<your-new-string>' dist/index.js`.
|
|
|
|
### 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`.
|
|
|
|
### Script sandbox npm deps need npm on the service PATH
|
|
Script actions CAN use npm packages — declare them via the CLI
|
|
(`action add/update --dependencies '[{"name":"axios","version":"1.6.0"}]'`
|
|
or shorthand `axios@1.6.0,dayjs`) and `require('axios')` works in-sandbox.
|
|
The backend installs deps into a cache dir (`/tmp/teable-automation-deps/<hash>`)
|
|
via `spawn('npm', ['install', ...])`, keyed by `name@version`.
|
|
|
|
**Gotcha:** systemd user services default to `PATH=/usr/local/bin:/usr/bin:...`
|
|
which OMITS the nvm bin dir where `npm` lives → scripts with deps fail with
|
|
`Dependency install failed: spawn npm ENOENT`. Fix: add
|
|
`Environment=PATH=/root/.nvm/versions/node/vX/bin:/usr/local/sbin:...` to
|
|
`teable-backend.service`, `daemon-reload`, restart. Verify:
|
|
`inkreach-teable workflow test <wfId>` on a script with `require('dayjs')` →
|
|
`succeeded`.
|
|
|
|
### 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 + 7 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=<ask-maintainer> 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=<ask-maintainer> 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=<ask-maintainer> 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;"
|
|
```
|