Files
inkreach-teable-cli/.agents/skills/inkreach-teable/guides/development.md
T
yeuimu e1a91dfa84 feat(cli+skill): make documented field UX real + fix skill doc drift
CLI (packages/inkreach-cli):
- new src/lib/field-normalize.ts: type aliases (sel/num/text/mul/…),
  select string-choices -> {name}, compact --fields 'Name:type:choices'
  expansion, smart type inference from --options shape / --name
  (--type now optional), name->ID resolution for foreignTableName /
  linkFieldName / lookupFieldName (incl. link-field foreignTableId
  backfill + unknown-key stripping), rating/number/button required-
  option defaults
- field create: +--description/--lookup-options/--is-lookup
- field update --options + table create --fields route through the
  normalizer; DEFAULT_ENDPOINT :3000 -> :3003
- 20 unit tests (field-normalize.spec.ts, vitest); live-verified:
  string choices, alias, foreignTableName, lookup-by-names, compact
  format, inference (priority->singleSelect, max:5->rating)

Skill docs (.agents/skills/inkreach-teable):
- ports :3000 -> :3003 everywhere (backend real port; :3000 is an
  unrelated container on this host)
- remove dead hr_admin account; note throwaway signup + real admin
- plugin register: actual URL-based flags; document plugin csp
  list/set; view/dashboard install requires config enable first
- field-types.md marked verified-2026-08 (aliases/inference/name
  resolution now implemented + tested)
- development.md: webpack dist/index.js cache trap; PGPASSWORD
  placeholder instead of wrong literal
2026-08-16 05:58:01 +00:00

6.7 KiB

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/:

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

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:

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.

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.

Test Infrastructure

Automation E2E tests live in scripts/automation-test/:

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

# 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;"