refactor: move skill to skills/inkreach-teable (from .agents/skills/)
git mv rename (history preserved); install.sh + README updated. The skill is consumed via user-level install (~/.zcode/skills), so in-repo .agents discovery was unnecessary.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# Development & Deployment
|
||||
|
||||
> This guide documents the **inkreach-teable application repo**
|
||||
> (`ssh://git@gitea.inkreach.cc:2222/inkreachhub/inkreach-teable.git`) — the
|
||||
> deployed instance this CLI operates on — not this CLI repo. Paths like
|
||||
> `apps/nestjs-backend/` refer to that repo's checkout.
|
||||
|
||||
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`.
|
||||
|
||||
### Workspace packages must be rebuilt BEFORE the backend (else stale dist ships)
|
||||
Distinct from the cache trap above: `apps/nestjs-backend` never compiles `@teable/*`
|
||||
sources — it resolves `@teable/core`, `@teable/v2-core`, `@teable/openapi` … via
|
||||
`node_modules` symlinks to `packages/*/dist`. `@teable/v2-core` in particular is a
|
||||
webpack EXTERNAL: the running backend reads `packages/v2/core/dist/index.cjs` directly
|
||||
at runtime, so a backend rebuild does not even copy it.
|
||||
|
||||
SILENT FAILURE MODE (hit 2026-08-18, select-defaultValue cascade fix): edit
|
||||
`packages/core/src/…` + `packages/v2/core/src/…` → only rebuild backend + restart →
|
||||
live API keeps the old behavior, no error anywhere. Correct order:
|
||||
|
||||
```bash
|
||||
pnpm --filter @teable/core build # tsc → packages/core/dist/**
|
||||
pnpm --filter @teable/v2-core build # tsdown → dist/index.cjs + dist/index.js
|
||||
cd apps/nestjs-backend && rm -f dist/index.js && pnpm build # bundle + externals
|
||||
# verify the new code actually shipped BEFORE restarting:
|
||||
grep '<new-string>' packages/core/dist/<path>.js
|
||||
grep '<new-string>' packages/v2/core/dist/index.cjs
|
||||
# then the best-path restart (:3003)
|
||||
```
|
||||
(Many packages at once: `pnpm -F './packages/**' build`, then backend build.)
|
||||
|
||||
### 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;"
|
||||
```
|
||||
Reference in New Issue
Block a user