i18n - docs translations (#20366)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
committed by
GitHub
parent
24e64350ee
commit
95bc8aea28
@@ -1,15 +1,15 @@
|
||||
---
|
||||
title: Application Config
|
||||
description: Declare your app's identity, default role, variables, and marketplace metadata with defineApplication.
|
||||
title: 应用程序配置
|
||||
description: 使用 defineApplication 声明应用的身份、默认角色、变量和 Marketplace 元数据。
|
||||
icon: rocket
|
||||
---
|
||||
|
||||
Every app must have exactly one `defineApplication` call. It declares:
|
||||
每个应用必须且只能有一个 `defineApplication` 调用。 它声明:
|
||||
|
||||
* **Identity** — universal identifier, display name, description.
|
||||
* **Permissions** — which role its logic functions and front components run under.
|
||||
* **Variables** *(optional)* — key–value pairs exposed to your code as environment variables.
|
||||
* **Pre-install / post-install hooks** *(optional)* — see [Logic Functions](/l/zh/developers/extend/apps/logic/logic-functions).
|
||||
* **Identity** — 通用标识符、显示名称、描述。
|
||||
* **Permissions** — 其逻辑函数和前端组件在何种角色下运行。
|
||||
* **Variables** *(optional)* — 以环境变量形式暴露给代码的键值对。
|
||||
* **Pre-install / post-install hooks** *(optional)* — 参见 [逻辑函数](/l/zh/developers/extend/apps/logic/logic-functions)。
|
||||
|
||||
```ts src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
@@ -31,35 +31,35 @@ export default defineApplication({
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
备注:
|
||||
|
||||
* `universalIdentifier` fields are deterministic IDs you own. Generate them once and keep them stable across syncs.
|
||||
* `applicationVariables` become environment variables for your functions and front components (e.g., `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` must reference a role defined with [`defineRole()`](/l/zh/developers/extend/apps/config/roles).
|
||||
* Pre-install and post-install functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`.
|
||||
* `universalIdentifier` 字段是你拥有的确定性 ID。 只需生成一次,并在多次同步过程中保持稳定不变。
|
||||
* `applicationVariables` 会变成你的函数和前端组件可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。
|
||||
* `defaultRoleUniversalIdentifier` 必须引用使用 [`defineRole()`](/l/zh/developers/extend/apps/config/roles) 定义的角色。
|
||||
* 在构建清单时会自动检测安装前/安装后函数——无需在 `defineApplication()` 中引用它们。
|
||||
|
||||
## Default function role
|
||||
## 默认函数角色
|
||||
|
||||
The `defaultRoleUniversalIdentifier` controls what the app's logic functions and front components can access:
|
||||
`defaultRoleUniversalIdentifier` 控制应用的逻辑函数和前端组件可以访问的内容:
|
||||
|
||||
* The runtime token injected as `TWENTY_APP_ACCESS_TOKEN` is derived from this role.
|
||||
* The typed API client is restricted to the permissions granted to that role.
|
||||
* Follow least-privilege: declare only the permissions your functions need.
|
||||
* 作为 `TWENTY_APP_ACCESS_TOKEN` 注入的运行时令牌来源于该角色。
|
||||
* 类型化 API 客户端将受限于授予该角色的权限。
|
||||
* 遵循最小权限原则:只声明你的函数所需的权限。
|
||||
|
||||
When you scaffold a new app, the CLI creates a starter role file at `src/roles/default-role.ts`. See [Roles & Permissions](/l/zh/developers/extend/apps/config/roles) for the full reference.
|
||||
当你使用脚手架创建新应用时,CLI 会在 `src/roles/default-role.ts` 中创建一个入门角色文件。 完整参考请参见 [角色与权限](/l/zh/developers/extend/apps/config/roles)。
|
||||
|
||||
## Marketplace metadata
|
||||
## 应用市场元数据
|
||||
|
||||
If you plan to [publish your app](/l/zh/developers/extend/apps/operations/publishing), these optional fields control how it appears in the marketplace:
|
||||
如果你计划[发布你的应用](/l/zh/developers/extend/apps/operations/publishing),这些可选字段将控制你的应用在应用市场中的展示:
|
||||
|
||||
| Field | Description |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| `author` | Author or company name |
|
||||
| `category` | App category for marketplace filtering |
|
||||
| `logoUrl` | Path to your app logo (e.g., `public/logo.png`) |
|
||||
| `screenshots` | Array of screenshot paths (e.g., `public/screenshot-1.png`) |
|
||||
| `aboutDescription` | Longer markdown description for the "About" tab. If omitted, the marketplace uses the package's `README.md` from npm |
|
||||
| `websiteUrl` | Link to your website |
|
||||
| `termsUrl` | Link to terms of service |
|
||||
| `emailSupport` | Support email address |
|
||||
| `issueReportUrl` | Link to issue tracker |
|
||||
| 字段 | 描述 |
|
||||
| ------------------ | -------------------------------------------------------------- |
|
||||
| `作者` | 作者或公司名称 |
|
||||
| `类别` | 用于应用市场筛选的应用类别 |
|
||||
| `logoUrl` | 应用徽标的路径(例如 `public/logo.png`) |
|
||||
| `screenshots` | 截图路径数组(例如 `public/screenshot-1.png`) |
|
||||
| `aboutDescription` | 用于“关于”选项卡的更长的 Markdown 描述。 如果省略,市场将使用该软件包在 npm 上的 `README.md`。 |
|
||||
| `websiteUrl` | 你的网站链接 |
|
||||
| `termsUrl` | 服务条款链接 |
|
||||
| `emailSupport` | 支持电子邮件地址 |
|
||||
| `issueReportUrl` | 问题跟踪器链接 |
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
title: Install Hooks
|
||||
description: Run logic before or after the install — seed data, back up records, validate the upgrade.
|
||||
title: 安装 Hooks
|
||||
description: 在安装之前或之后运行逻辑——播种数据、备份记录、验证升级。
|
||||
icon: wrench
|
||||
---
|
||||
|
||||
Install hooks are special logic functions that run during the install or upgrade lifecycle. They share the same handler runtime as regular [logic functions](/l/zh/developers/extend/apps/logic/logic-functions) and receive an `InstallPayload`, but they're declared with their own define functions — `definePostInstallLogicFunction()` and `definePreInstallLogicFunction()` — and live outside the normal trigger model (HTTP, cron, database events).
|
||||
安装 hooks 是在安装或升级生命周期期间运行的特殊逻辑函数。 它们与常规的[逻辑函数](/l/zh/developers/extend/apps/logic/logic-functions)共享相同的处理程序运行时,并接收 `InstallPayload`,但它们使用自己的定义函数声明——`definePostInstallLogicFunction()` 和 `definePreInstallLogicFunction()`——并且存在于普通触发模型(HTTP、cron、数据库事件)之外。
|
||||
|
||||
Each app may define **at most one pre-install** and **at most one post-install** function. The manifest build will error if more than one of either is detected.
|
||||
每个应用**最多只能定义一个安装前函数**和**最多一个安装后函数**。 如果检测到多个其中任意一种,清单构建将报错。
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
@@ -20,9 +20,9 @@ Each app may define **at most one pre-install** and **at most one post-install**
|
||||
```
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="definePostInstallLogicFunction" description="Runs after the workspace metadata migration is applied">
|
||||
<Accordion title="definePostInstallLogicFunction" description="在应用工作区元数据迁移之后运行">
|
||||
|
||||
A post-install function runs automatically once your app has finished installing on a workspace. The server executes it **after** the app's metadata has been synchronized and the SDK client has been generated, so the workspace is fully ready to use and the new schema is in place. Typical use cases include seeding default data, creating initial records, configuring workspace settings, or provisioning resources on third-party services.
|
||||
安装后函数会在你的应用完成安装到某个工作区后自动运行。 服务器会在应用的元数据已同步并已生成 SDK 客户端**之后**执行它,因此工作区已完全可用,且新架构已就绪。 常见用例包括预置默认数据、创建初始记录、配置工作区设置,或在第三方服务上预配资源。
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
@@ -42,30 +42,30 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
You can also manually execute the post-install function at any time using the CLI:
|
||||
你也可以随时使用 CLI 手动执行安装后函数:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
Key points:
|
||||
* Post-install functions use `definePostInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
|
||||
* The handler receives an `InstallPayload` with `{ previousVersion?: string; newVersion: string }` — `newVersion` is the version being installed, and `previousVersion` is the version that was previously installed (or `undefined` on a fresh install). Use these values to distinguish fresh installs from upgrades and to run version-specific migration logic.
|
||||
* **When the hook runs**: on fresh installs only, by default. Pass `shouldRunOnVersionUpgrade: true` if you also want it to run when the app is upgraded from a previous version. When omitted, the flag defaults to `false` and upgrades skip the hook.
|
||||
* **Execution model — async by default, sync opt-in**: the `shouldRunSynchronously` flag controls *how* post-install is executed.
|
||||
* `shouldRunSynchronously: false` *(default)* — the hook is **enqueued on the message queue** with `retryLimit: 3` and runs asynchronously in a worker. The install response returns as soon as the job is enqueued, so a slow or failing handler does not block the caller. The worker will retry up to three times. **Use this for long-running jobs** — seeding large datasets, calling slow third-party APIs, provisioning external resources, anything that might exceed a reasonable HTTP response window.
|
||||
* `shouldRunSynchronously: true` — the hook is executed **inline during the install flow** (same executor as pre-install). The install request blocks until the handler finishes, and if it throws, the install caller receives a `POST_INSTALL_ERROR`. No automatic retries. **Use this for fast, must-complete-before-response work** — for example, emitting a validation error to the user, or quick setup that the client will rely on immediately after the install call returns. Keep in mind the metadata migration has already been applied by the time post-install runs, so a sync-mode failure does **not** roll back the schema changes — it only surfaces the error.
|
||||
* Make sure your handler is idempotent. In async mode the queue may retry up to three times; in either mode the hook may run again on upgrades when `shouldRunOnVersionUpgrade: true`.
|
||||
* The environment variables `APPLICATION_ID`, `APP_ACCESS_TOKEN`, and `API_URL` are available inside the handler (same as any other logic function), so you can call the Twenty API with an application access token scoped to your app.
|
||||
* Only one post-install function is allowed per application. The manifest build will error if more than one is detected.
|
||||
* The function's `universalIdentifier`, `shouldRunOnVersionUpgrade`, and `shouldRunSynchronously` are automatically attached to the application manifest under the `postInstallLogicFunction` field during the build — you do not need to reference them in [`defineApplication()`](/l/zh/developers/extend/apps/config/application).
|
||||
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
|
||||
* **Not executed in dev mode**: when an app is registered locally (via `yarn twenty dev`), the server skips the install flow entirely and syncs files directly through the CLI watcher — so post-install never runs in dev mode, regardless of `shouldRunSynchronously`. Use `yarn twenty exec --postInstall` to trigger it manually against a running workspace.
|
||||
关键点:
|
||||
* 安装后函数使用 `definePostInstallLogicFunction()` — 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`toolTriggerSettings`、`workflowActionTriggerSettings`)的专用变体。
|
||||
* 处理程序会接收一个 `InstallPayload`,其为 `{ previousVersion?: string; newVersion: string }` —— `newVersion` 是正在安装的版本,而 `previousVersion` 是先前已安装的版本(在全新安装时为 `undefined`)。 使用这些值来区分全新安装与升级,并运行特定版本的迁移逻辑。
|
||||
* **钩子何时运行**:默认情况下,仅在全新安装时运行。 如果还希望在应用从旧版本升级时运行,请传入 `shouldRunOnVersionUpgrade: true`。 若省略,该标志默认为 `false`,升级将跳过该钩子。
|
||||
* **执行模型 — 默认异步,可选择同步**:`shouldRunSynchronously` 标志控制安装后*如何*执行。
|
||||
* `shouldRunSynchronously: false` *(默认)* — 该钩子会**加入消息队列**,设置 `retryLimit: 3`,并在工作线程中异步运行。 作业一入列,安装响应即返回,因此缓慢或失败的处理程序不会阻塞调用方。 工作线程最多会重试三次。 **将其用于长时间运行的作业**——预填充大型数据集、调用缓慢的第三方 API、预配外部资源,以及任何可能超出合理 HTTP 响应窗口的任务。
|
||||
* `shouldRunSynchronously: true` — 该钩子会在安装流程中**内联执行**(与安装前使用相同的执行器)。 安装请求将阻塞直至处理程序完成;若抛出异常,安装调用方将收到 `POST_INSTALL_ERROR`。 不进行自动重试。 **用于需要在响应前完成的快速工作**——例如向用户返回验证错误,或进行安装调用返回后客户端将立即依赖的快速设置。 请注意,运行安装后时,元数据迁移已应用完成,因此同步模式下的失败**不会**回滚架构更改——它只会暴露错误。
|
||||
* 确保你的处理程序是幂等的。 在异步模式下,队列最多可重试三次;在任一模式下,当 `shouldRunOnVersionUpgrade: true` 时,该钩子在升级时可能再次运行。
|
||||
* 在处理程序内可使用环境变量 `APPLICATION_ID`、`APP_ACCESS_TOKEN` 和 `API_URL`(与其他逻辑函数相同),因此你可以使用作用域限定到你应用的应用访问令牌调用 Twenty API。
|
||||
* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。
|
||||
* 构建期间,函数的 `universalIdentifier`、`shouldRunOnVersionUpgrade` 和 `shouldRunSynchronously` 会自动附加到应用清单的 `postInstallLogicFunction` 字段下——你无需在 [`defineApplication()`](/l/zh/developers/extend/apps/config/application) 中引用它们。
|
||||
* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的设置任务,如数据填充。
|
||||
* **开发模式下不执行**:当应用在本地注册(通过 `yarn twenty dev`)时,服务器会完全跳过安装流程,并通过 CLI 监视器直接同步文件——因此无论 `shouldRunSynchronously` 如何,安装后在开发模式下都不会运行。 使用 `yarn twenty exec --postInstall` 在运行中的工作区上手动触发它。
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePreInstallLogicFunction" description="Runs before the workspace metadata migration is applied">
|
||||
<Accordion title="definePreInstallLogicFunction" description="在应用工作区元数据迁移之前运行">
|
||||
|
||||
A pre-install function runs automatically during installation, **before the workspace metadata migration is applied**. It shares the same payload shape as post-install (`InstallPayload`), but it is positioned earlier in the install flow so it can prepare state that the upcoming migration depends on — typical uses include backing up data, validating compatibility with the new schema, or archiving records that are about to be restructured or dropped.
|
||||
安装前函数会在安装期间自动运行,**在应用工作区元数据迁移之前**。 它与安装后共享相同的负载结构(`InstallPayload`),但在安装流程中位置更早,因此可以准备即将到来的迁移所依赖的状态——典型用例如备份数据、验证与新架构的兼容性,或归档即将被重构或删除的记录。
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
@@ -84,35 +84,35 @@ export default definePreInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
You can also manually execute the pre-install function at any time using the CLI:
|
||||
你也可以随时使用 CLI 手动执行安装前函数:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --preInstall
|
||||
```
|
||||
|
||||
Key points:
|
||||
* Pre-install functions use `definePreInstallLogicFunction()` — same specialized config as post-install, just attached to a different lifecycle slot.
|
||||
* Both pre- and post-install handlers receive the same `InstallPayload` type: `{ previousVersion?: string; newVersion: string }`. Import it once and reuse it for both hooks.
|
||||
* **When the hook runs**: positioned just before the workspace metadata migration (`synchronizeFromManifest`). Before executing, the server runs a purely additive "pared-down sync" that registers the **new** version's pre-install function in the workspace metadata — nothing else is touched — and then executes it. Because this sync is additive-only, the previous version's objects, fields, and data are still intact when your handler runs: you can safely read and back up pre-migration state.
|
||||
* **Execution model**: pre-install is executed **synchronously** and **blocks the install**. If the handler throws, the install is aborted before any schema changes are applied — the workspace stays on the previous version in a consistent state. This is intentional: pre-install is your last chance to refuse a risky upgrade.
|
||||
* As with post-install, only one pre-install function is allowed per application. It is attached to the application manifest under `preInstallLogicFunction` automatically during the build.
|
||||
* **Not executed in dev mode**: same as post-install — the install flow is skipped entirely for locally-registered apps, so pre-install never runs under `yarn twenty dev`. Use `yarn twenty exec --preInstall` to trigger it manually.
|
||||
关键点:
|
||||
* 安装前函数使用 `definePreInstallLogicFunction()`——与安装后相同的专用配置,只是附加到不同的生命周期阶段。
|
||||
* 安装前和安装后处理程序接收相同的 `InstallPayload` 类型:`{ previousVersion?: string; newVersion: string }`。 导入一次,可在两个钩子中复用。
|
||||
* **钩子何时运行**:位于工作区元数据迁移(`synchronizeFromManifest`)之前。 在执行之前,服务器会运行一次纯增量的“精简同步”,将**新**版本的安装前函数注册到工作区元数据中——不会触及其他任何内容——然后再执行它。 由于此次同步仅为增量操作,当你的处理程序运行时,上一版本的对象、字段和数据仍完好无损:你可以安全地读取并备份迁移前的状态。
|
||||
* **执行模型**:安装前以**同步**方式执行,并且**会阻塞安装**。 如果处理程序抛出异常,安装会在任何架构更改应用之前被中止——工作区将保持在上一版本且处于一致状态。 这是有意为之:安装前是你拒绝高风险升级的最后机会。
|
||||
* 与安装后相同,每个应用仅允许一个安装前函数。 在构建期间,它会自动附加到应用清单的 `preInstallLogicFunction` 下。
|
||||
* **开发模式下不执行**:与安装后相同——对于本地注册的应用将完全跳过安装流程,因此在 `yarn twenty dev` 下不会运行安装前。 使用 `yarn twenty exec --preInstall` 手动触发它。
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Pre-install vs post-install: when to use which" description="Choosing the right install hook">
|
||||
<Accordion title="安装前 vs 安装后:何时使用哪一个" description="选择合适的安装钩子">
|
||||
|
||||
Both hooks are part of the same install flow and receive the same `InstallPayload`. The difference is **when** they run relative to the workspace metadata migration, and that changes what data they can safely touch.
|
||||
两个钩子都属于同一安装流程,并接收相同的 `InstallPayload`。 区别在于它们相对于工作区元数据迁移**何时**运行,这会影响它们可以安全访问的数据范围。
|
||||
|
||||
Pre-install is always **synchronous** (it blocks the install and can abort it). Post-install is **asynchronous by default** — enqueued on a worker with automatic retries — but can opt into synchronous execution with `shouldRunSynchronously: true`. See the `definePostInstallLogicFunction` accordion above for when to use each mode.
|
||||
安装前始终为**同步**(会阻塞安装并可中止它)。 安装后**默认异步**——在工作线程中入列并自动重试——但可通过 `shouldRunSynchronously: true` 选择同步执行。 关于各模式的使用场景,请参见上方的 `definePostInstallLogicFunction` 折叠面板。
|
||||
|
||||
**Use `post-install` for anything that needs the new schema to exist.** This is the common case:
|
||||
**对于需要新架构已存在的任何事项,请使用 `post-install`。** 这是最常见的情况:
|
||||
|
||||
* Seeding default data (creating initial records, default views, demo content) against newly-added objects and fields.
|
||||
* Registering webhooks with third-party services now that the app has its credentials.
|
||||
* Calling your own API to finish setup that depends on the synchronized metadata.
|
||||
* Idempotent "ensure this exists" logic that should reconcile state on every upgrade — combine with `shouldRunOnVersionUpgrade: true`.
|
||||
* 针对新添加的对象和字段预填充默认数据(创建初始记录、默认视图、演示内容)。
|
||||
* 在应用已有凭据的前提下,向第三方服务注册 Webhook。
|
||||
* 调用你自己的 API 完成依赖已同步元数据的设置。
|
||||
* 用于在每次升级时对状态进行对账的幂等“确保其存在”逻辑——结合 `shouldRunOnVersionUpgrade: true` 使用。
|
||||
|
||||
Example — seed a default `PostCard` record after install:
|
||||
示例——在安装后预填充一个默认的 `PostCard` 记录:
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
@@ -137,14 +137,14 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
**Use `pre-install` when a migration would otherwise destroy or corrupt existing data.** Because pre-install runs against the *previous* schema and its failure rolls back the upgrade, it is the right place for anything risky:
|
||||
**当迁移可能破坏或损坏现有数据时,请使用 `pre-install`。** 由于安装前在*先前*架构上运行,且其失败会回滚升级,因此凡是有风险的操作都应放在这里:
|
||||
|
||||
* **Backing up data that is about to be dropped or restructured** — e.g. you are removing a field in v2 and need to copy its values into another field or export them to storage before the migration runs.
|
||||
* **Archiving records that a new constraint would invalidate** — e.g. a field is becoming `NOT NULL` and you need to delete or fix rows with null values first.
|
||||
* **Validating compatibility and refusing the upgrade if the current data cannot be migrated cleanly** — throw from the handler and the install aborts with no changes applied. This is safer than discovering the incompatibility mid-migration.
|
||||
* **Renaming or rekeying data** ahead of a schema change that would lose the association.
|
||||
* **备份即将被删除或重构的数据**——例如,你在 v2 中移除某个字段,需要在迁移运行前将其值复制到另一个字段或导出到存储中。
|
||||
* **归档会被新约束判为无效的记录**——例如某个字段将变为 `NOT NULL`,你需要先删除或修正具有空值的行。
|
||||
* **验证兼容性;若当前数据无法干净迁移则拒绝升级**——从处理程序中抛出异常,安装将中止且不会应用任何更改。 这比在迁移中途才发现不兼容要更安全。
|
||||
* **在会导致关联丢失的架构更改之前**对数据进行重命名或重新设置键。
|
||||
|
||||
Example — archive records before a destructive migration:
|
||||
示例——在破坏性迁移之前归档记录:
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
@@ -186,20 +186,20 @@ export default definePreInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
**Rule of thumb:**
|
||||
**经验法则:**
|
||||
|
||||
| You want to... | Use |
|
||||
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| Seed default data, configure the workspace, register external resources | `post-install` |
|
||||
| Run long-running seeding or third-party calls that shouldn't block the install response | `post-install` (default — `shouldRunSynchronously: false`, with worker retries) |
|
||||
| Run fast setup that the caller will rely on immediately after the install call returns | `post-install` with `shouldRunSynchronously: true` |
|
||||
| Read or back up data that the upcoming migration would lose | `pre-install` |
|
||||
| Reject an upgrade that would corrupt existing data | `pre-install` (throw from the handler) |
|
||||
| Run reconciliation on every upgrade | `post-install` with `shouldRunOnVersionUpgrade: true` |
|
||||
| Do one-off setup on the first install only | `post-install` with `shouldRunOnVersionUpgrade: false` (default) |
|
||||
| 你想要... | 使用 |
|
||||
| ----------------------- | ------------------------------------------------------------- |
|
||||
| 预填充默认数据、配置工作区、注册外部资源 | `post-install` |
|
||||
| 运行不应阻塞安装响应的长时间预填充或第三方调用 | `post-install` (默认 — `shouldRunSynchronously: false`,由工作线程重试) |
|
||||
| 运行安装调用返回后调用方将立即依赖的快速设置 | `post-install`,配合 `shouldRunSynchronously: true` |
|
||||
| 读取或备份即将被迁移丢失的数据 | `pre-install` |
|
||||
| 拒绝会损坏现有数据的升级 | `pre-install`(从处理程序中抛出异常) |
|
||||
| 在每次升级时执行对账 | `post-install` 配合 `shouldRunOnVersionUpgrade: true` |
|
||||
| 仅在首次安装时执行一次性设置 | `post-install` 配合 `shouldRunOnVersionUpgrade: false`(默认) |
|
||||
|
||||
<Note>
|
||||
If in doubt, default to **post-install**. Only reach for pre-install when the migration itself is destructive and you need to intercept the previous state before it is gone.
|
||||
如有不确定,默认选择**安装后(post-install)**。 仅当迁移本身具有破坏性,且你需要在其丢失之前拦截先前状态时,才使用安装前。
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Configure the app itself — its identity, default permissions, and what runs at install time.
|
||||
title: 概览
|
||||
description: 配置应用本身——它的身份、默认权限,以及在安装时运行的内容。
|
||||
icon: screwdriver-wrench
|
||||
---
|
||||
|
||||
A Twenty app's **config layer** is what describes the app *to the platform* — its identity, the permissions it holds, and the code that runs during install or upgrade. These declarations don't add new data shapes or runtime behavior; they tell Twenty *who the app is* and *how to set it up*.
|
||||
Twenty 应用的 **配置层(config layer)** 用来向 *平台* 描述应用——它的身份、所持有的权限,以及在安装或升级期间运行的代码。 这些声明不会添加新的数据结构或运行时行为;它们告诉 Twenty *这个应用是谁*,以及 *如何完成设置*。
|
||||
|
||||
```text
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
@@ -26,26 +26,26 @@ A Twenty app's **config layer** is what describes the app *to the platform* —
|
||||
└──────────────────────────────────┘
|
||||
```
|
||||
|
||||
## In this section
|
||||
## 本节内容
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Application Config" icon="rocket" href="/l/zh/developers/extend/apps/config/application">
|
||||
`defineApplication` — identity, default role, variables, marketplace metadata.
|
||||
<Card title="应用程序配置" icon="rocket" href="/l/zh/developers/extend/apps/config/application">
|
||||
`defineApplication` —— 身份、默认角色、变量、应用市场元数据。
|
||||
</Card>
|
||||
<Card title="Roles & Permissions" icon="shield-halved" href="/l/zh/developers/extend/apps/config/roles">
|
||||
`defineRole` — declare what your app's logic functions can read and write.
|
||||
<Card title="角色和权限" icon="shield-halved" href="/l/zh/developers/extend/apps/config/roles">
|
||||
`defineRole` —— 声明应用的逻辑函数可以读取和写入什么。
|
||||
</Card>
|
||||
<Card title="Install Hooks" icon="wrench" href="/l/zh/developers/extend/apps/config/install-hooks">
|
||||
`definePreInstallLogicFunction` and `definePostInstallLogicFunction` — back up data, seed defaults, validate upgrades.
|
||||
<Card title="安装钩子" icon="wrench" href="/l/zh/developers/extend/apps/config/install-hooks">
|
||||
`definePreInstallLogicFunction` 和 `definePostInstallLogicFunction` —— 备份数据、写入默认值、验证升级。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## How the pieces relate
|
||||
## 各部分之间的关系
|
||||
|
||||
* **Application** is the entry point. Every app has exactly one `defineApplication()` call, and it points at one **Role** as its default.
|
||||
* The **Role** controls what the app's logic functions and front components can read and write. Follow least-privilege: only grant the permissions your code actually needs.
|
||||
* **Install Hooks** run during install or upgrade — pre-install before the metadata migration (so it can refuse a risky upgrade), post-install after the migration (so it can seed default data against the new schema).
|
||||
* **Application** 是入口点。 每个应用都有且仅有一次 `defineApplication()` 调用,并将一个 **Role** 作为其默认角色。
|
||||
* **Role** 控制应用的逻辑函数和前端组件可以读取和写入什么。 遵循最小权限原则:只授予代码实际需要的权限。
|
||||
* **Install Hooks(安装钩子)** 在安装或升级过程中运行——预安装钩子在元数据迁移之前运行(因此可以拒绝存在风险的升级),后安装钩子在迁移之后运行(因此可以根据新模式写入默认数据)。
|
||||
|
||||
<Note>
|
||||
Install hooks share the [logic function](/l/zh/developers/extend/apps/logic/logic-functions) runtime — same handler signature, same environment variables, same typed API client — but they're declared with their own define functions and live outside the regular trigger model (HTTP, cron, database events).
|
||||
安装钩子与 [logic function](/l/zh/developers/extend/apps/logic/logic-functions) 共用运行时——相同的处理器签名、相同的环境变量、相同的类型化 API 客户端——但它们使用各自的 define 函数声明,并且位于常规触发模型(HTTP、cron、数据库事件)之外。
|
||||
</Note>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
---
|
||||
title: Public Assets
|
||||
description: Ship static files — images, icons, fonts — alongside your app via the public/ folder.
|
||||
title: 公共资源
|
||||
description: 通过 public/ 文件夹将静态文件(图像、图标、字体)与应用一起发布。
|
||||
icon: folder-open
|
||||
---
|
||||
|
||||
The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server.
|
||||
应用根目录中的 `public/` 文件夹包含静态文件——图像、图标、字体,或应用在运行时所需的任何其他资源。 这些文件会在构建时自动包含、在开发模式下同步,并上传到服务器。
|
||||
|
||||
Files placed in `public/` are:
|
||||
放置在 `public/` 中的文件:
|
||||
|
||||
* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them.
|
||||
* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components.
|
||||
* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic.
|
||||
* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published.
|
||||
* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed.
|
||||
* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output.
|
||||
* **公开可访问**——同步到服务器后,资源将通过公共 URL 提供服务。 访问它们无需身份验证。
|
||||
* **在前端组件中可用**——使用资源 URL 在 React 组件中显示图像、图标或任何媒体。
|
||||
* **在逻辑函数中可用**——在电子邮件、API 响应或任何服务端逻辑中引用资源 URL。
|
||||
* **用于市场元数据**——`defineApplication()` 中的 `logoUrl` 和 `screenshots` 字段引用此文件夹中的文件(例如,`public/logo.png`)。 应用发布后,这些内容会显示在市场中。
|
||||
* **在开发模式下自动同步**——当在 `public/` 中添加、更新或删除文件时,会自动同步到服务器。 无需重启。
|
||||
* **包含在构建中**——`yarn twenty build` 会将所有公共资源打包到分发产物中。
|
||||
|
||||
## Accessing public assets with `getPublicAssetUrl`
|
||||
## 使用 `getPublicAssetUrl` 访问公共资源
|
||||
|
||||
Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**.
|
||||
使用来自 `twenty-sdk` 的 `getPublicAssetUrl` 辅助函数获取 `public/` 目录中文件的完整 URL。 它可在 **逻辑函数** 和 **前端组件** 中使用。
|
||||
|
||||
**In a logic function:**
|
||||
**在逻辑函数中:**
|
||||
|
||||
```ts src/logic-functions/send-invoice.ts
|
||||
import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
@@ -44,7 +44,7 @@ export default defineLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
**In a front component:**
|
||||
**在前端组件中:**
|
||||
|
||||
```tsx src/front-components/company-card.tsx
|
||||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
@@ -56,4 +56,4 @@ export default defineFrontComponent(() => {
|
||||
});
|
||||
```
|
||||
|
||||
The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present.
|
||||
`path` 参数是相对于应用的 `public/` 文件夹的。 `getPublicAssetUrl('logo.png')` 和 `getPublicAssetUrl('public/logo.png')` 均解析为相同的 URL——如果存在,`public/` 前缀会被自动移除。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Roles & Permissions
|
||||
description: Declare what objects and fields your app's logic functions and front components can read and write.
|
||||
title: 角色和权限
|
||||
description: 声明你的应用的逻辑函数和前端组件可以读取和写入哪些对象和字段。
|
||||
icon: shield-halved
|
||||
---
|
||||
|
||||
A **role** is a permission set: which objects an app can read or write, which fields it can see, and which platform-level capabilities it can use. Every app's logic functions and front components inherit the permissions of the role declared as `defaultRoleUniversalIdentifier` in [`defineApplication`](/l/zh/developers/extend/apps/config/application).
|
||||
**角色(role)** 是一组权限:应用可以读取或写入哪些对象,可以查看哪些字段,以及可以使用哪些平台级功能。 每个应用的逻辑函数和前端组件都会继承在 [`defineApplication`](/l/zh/developers/extend/apps/config/application) 中被声明为 `defaultRoleUniversalIdentifier` 的角色的权限。
|
||||
|
||||
```ts src/roles/restricted-company-role.ts
|
||||
import {
|
||||
@@ -49,9 +49,9 @@ export default defineRole({
|
||||
});
|
||||
```
|
||||
|
||||
## The default function role
|
||||
## 默认函数角色
|
||||
|
||||
When you scaffold a new app, the CLI creates a default role file:
|
||||
当你使用脚手架创建新应用时,CLI 会创建一个默认角色文件:
|
||||
|
||||
```ts src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
|
||||
@@ -77,14 +77,14 @@ export default defineRole({
|
||||
});
|
||||
```
|
||||
|
||||
This role's `universalIdentifier` is referenced from `application-config.ts` as `defaultRoleUniversalIdentifier`:
|
||||
该角色的 `universalIdentifier` 会在 `application-config.ts` 中被引用为 `defaultRoleUniversalIdentifier`:
|
||||
|
||||
* **`*.role.ts`** declares what the role can do.
|
||||
* **`application-config.ts`** points to that role so your functions inherit its permissions.
|
||||
* **`*.role.ts`** 声明该角色可以执行的操作。
|
||||
* **`application-config.ts`** 指向该角色,使你的函数继承其权限。
|
||||
|
||||
## Best practices
|
||||
## 最佳实践
|
||||
|
||||
* Start from the scaffolded role, then progressively restrict it — the default grants broad read access, which is rarely what you want in production.
|
||||
* Replace `objectPermissions` and `fieldPermissions` with the exact objects and fields your functions actually need.
|
||||
* `permissionFlags` control access to platform-level capabilities. Keep them minimal.
|
||||
* See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
* 从脚手架生成的角色开始,然后逐步收紧它的权限——默认角色授予广泛的读取访问权限,而这在生产环境中很少是你想要的。
|
||||
* 将 `objectPermissions` 和 `fieldPermissions` 替换为你的函数实际需要的精确对象和字段。
|
||||
* `permissionFlags` 控制对平台级能力的访问。 尽量保持最小化。
|
||||
* 查看一个可运行示例:[`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts)。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Extending Objects
|
||||
description: Add fields to standard Twenty objects (Person, Company, …) or to objects from other apps using defineField.
|
||||
title: 扩展对象
|
||||
description: 向标准 Twenty 对象添加字段(Person、Company 等) 或使用 defineField 向其他应用的对象添加字段。
|
||||
icon: wand-magic-sparkles
|
||||
---
|
||||
|
||||
Use `defineField()` to add a field to an object you don't own — a standard Twenty object like Person or Company, or an object shipped by another installed app. Unlike inline fields declared inside [`defineObject`](/l/zh/developers/extend/apps/data/objects), standalone fields require an `objectUniversalIdentifier` to specify which object they extend.
|
||||
使用 `defineField()` 为你不拥有的对象添加字段——例如像 Person 或 Company 这样的标准 Twenty 对象,或由其他已安装应用提供的对象。 与在 [`defineObject`](/l/zh/developers/extend/apps/data/objects) 中声明的内联字段不同,独立字段需要一个 `objectUniversalIdentifier` 来指定它们要扩展的对象。
|
||||
|
||||
```ts src/fields/company-loyalty-tier.field.ts
|
||||
import { defineField, FieldType } from 'twenty-sdk/define';
|
||||
@@ -24,9 +24,9 @@ export default defineField({
|
||||
});
|
||||
```
|
||||
|
||||
## Key points
|
||||
## 关键点
|
||||
|
||||
* `objectUniversalIdentifier` identifies the target object. For standard Twenty objects, import the constant from `twenty-sdk`:
|
||||
* `objectUniversalIdentifier` 用于标识目标对象。 对于标准 Twenty 对象,从 `twenty-sdk` 导入常量:
|
||||
|
||||
```ts
|
||||
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
|
||||
@@ -37,12 +37,12 @@ export default defineField({
|
||||
// …
|
||||
```
|
||||
|
||||
* When defining fields **inline inside `defineObject()`**, you do **not** need `objectUniversalIdentifier` — it's inherited from the parent object.
|
||||
* 在 **`defineObject()` 内联定义字段** 时,**不需要** `objectUniversalIdentifier`——它会从父对象继承。
|
||||
|
||||
* `defineField()` is the only way to add fields to objects you didn't create with `defineObject()`.
|
||||
* `defineField()` 是为非通过 `defineObject()` 创建的对象添加字段的唯一方式。
|
||||
|
||||
* File location is up to you. The convention is `src/fields/\<name>.field.ts`, but the SDK detects fields anywhere in `src/`.
|
||||
* 文件位置由你决定。 约定是使用 `src/fields/\<name>.field.ts`,但 SDK 会在整个 `src/` 中检测字段。
|
||||
|
||||
## Adding a relation to an existing object
|
||||
## 向现有对象添加关系
|
||||
|
||||
To add a relation field (e.g. linking your custom object to a standard `Person`), use `defineField()` with `FieldType.RELATION`. The pattern is the same as for inline relations but with `objectUniversalIdentifier` set explicitly. See [Relations](/l/zh/developers/extend/apps/data/relations) for the bidirectional pattern.
|
||||
要添加关系字段(例如将你的自定义对象链接到标准 `Person`),请将 `defineField()` 与 `FieldType.RELATION` 一起使用。 模式与内联关系相同,但需要显式设置 `objectUniversalIdentifier`。 有关双向模式,请参见[关系](/l/zh/developers/extend/apps/data/relations)。
|
||||
|
||||
@@ -4,7 +4,7 @@ description: 使用 defineObject 声明新的记录类型——具有其自身
|
||||
icon: 表格
|
||||
---
|
||||
|
||||
Custom **objects** are new record types your app adds to a workspace — Post Card, Invoice, Subscription, anything specific to your domain. Each object declares its schema (fields, relations, default values) and a stable universal identifier that survives across syncs and deploys.
|
||||
自定义**对象**是你的应用在工作区中添加的新记录类型——Post Card、Invoice、Subscription,或任何特定于你业务领域的内容。 每个对象都会声明其模式(字段、关系、默认值)以及一个在多次同步和部署中保持稳定的通用标识符。
|
||||
|
||||
```ts src/objects/post-card.object.ts
|
||||
import { defineObject, FieldType } from 'twenty-sdk/define';
|
||||
@@ -74,20 +74,20 @@ export default defineObject({
|
||||
});
|
||||
```
|
||||
|
||||
## Key points
|
||||
## 关键点
|
||||
|
||||
* `universalIdentifier` 必须在各次部署间保持唯一且稳定。
|
||||
* 每个字段都需要 `name`、`type`、`label` 以及其自身稳定的 `universalIdentifier`。
|
||||
* `fields` 数组是可选的——你可以定义没有自定义字段的对象。
|
||||
* Inline fields defined here do **not** need an `objectUniversalIdentifier` — it's inherited from the parent object. Use [`defineField()`](/l/zh/developers/extend/apps/data/extending-objects) to add fields to objects you don't own.
|
||||
* You can scaffold new objects with `yarn twenty add object`, which guides you through naming, fields, and relationships. See [Architecture → Scaffolding entities](/l/zh/developers/extend/apps/getting-started/scaffolding).
|
||||
* 此处定义的内联字段**不**需要 `objectUniversalIdentifier`——它会从父对象继承。 使用[`defineField()`](/l/zh/developers/extend/apps/data/extending-objects)为你不拥有的对象添加字段。
|
||||
* 你可以使用 `yarn twenty add object` 脚手架创建新对象,它会引导你完成命名、字段和关系。 参见 [Architecture → Scaffolding entities](/l/zh/developers/extend/apps/getting-started/scaffolding)。
|
||||
|
||||
<Note>
|
||||
**Base fields are added automatically.** When you define a custom object, Twenty creates standard fields like `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`, and `deletedAt` for you. You don't need to declare them in your `fields` array — only your custom fields. You can override a default field by declaring one with the same name, but this is rarely a good idea.
|
||||
**基础字段会自动添加。** 当你定义自定义对象时,Twenty 会为你创建标准字段,例如 `id`、`name`、`createdAt`、`updatedAt`、`createdBy`、`updatedBy` 和 `deletedAt`。 你无需在 `fields` 数组中声明这些字段——只需声明你的自定义字段。 你可以通过声明一个同名字段来覆盖默认字段,但这么做通常并不是一个好主意。
|
||||
</Note>
|
||||
|
||||
## What's next
|
||||
## 接下来
|
||||
|
||||
* **Connect this object to others** — see [Relations](/l/zh/developers/extend/apps/data/relations) for the bidirectional relation pattern.
|
||||
* **Add fields to objects from other apps** — see [Extending Objects](/l/zh/developers/extend/apps/data/extending-objects) for `defineField()`.
|
||||
* **Display this object in the UI** — see [Views](/l/zh/developers/extend/apps/layout/views) and [Navigation Menu Items](/l/zh/developers/extend/apps/layout/navigation-menu-items) to put it in the sidebar.
|
||||
* **将此对象与其他对象关联**——关于双向关系模式,参见 [Relations](/l/zh/developers/extend/apps/data/relations)。
|
||||
* **为其他应用的对象添加字段**——关于 `defineField()`,参见 [Extending Objects](/l/zh/developers/extend/apps/data/extending-objects)。
|
||||
* **在 UI 中展示此对象**——参见 [Views](/l/zh/developers/extend/apps/layout/views) 和 [Navigation Menu Items](/l/zh/developers/extend/apps/layout/navigation-menu-items),将其放入侧边栏。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Shape the data your app adds to a workspace — objects, fields, and relations.
|
||||
title: 概览
|
||||
description: 定义你的应用在工作区中添加的数据结构——对象、字段和关系。
|
||||
icon: database
|
||||
---
|
||||
|
||||
A Twenty app's **data layer** is the data your app *adds* to a workspace — the new record types it declares, the columns it adds to existing objects, and how those records connect to each other.
|
||||
Twenty 应用的 **数据层(data layer)** 是你的应用*添加*到工作区中的数据——它声明的新记录类型、它为现有对象添加的列,以及这些记录之间如何相互关联。
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────────────┐
|
||||
@@ -23,30 +23,30 @@ A Twenty app's **data layer** is the data your app *adds* to a workspace — the
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## In this section
|
||||
## 本节内容
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Objects" icon="table" href="/l/zh/developers/extend/apps/data/objects">
|
||||
`defineObject` — declare new record types with their own fields.
|
||||
<Card title="对象" icon="表格" href="/l/zh/developers/extend/apps/data/objects">
|
||||
`defineObject` —— 使用其自有字段声明新的记录类型。
|
||||
</Card>
|
||||
<Card title="Extending Objects" icon="wand-magic-sparkles" href="/l/zh/developers/extend/apps/data/extending-objects">
|
||||
`defineField` — add fields to standard or other apps' objects.
|
||||
<Card title="扩展对象" icon="wand-magic-sparkles" href="/l/zh/developers/extend/apps/data/extending-objects">
|
||||
`defineField` —— 向标准对象或其他应用的对象添加字段。
|
||||
</Card>
|
||||
<Card title="Relations" icon="diagram-project" href="/l/zh/developers/extend/apps/data/relations">
|
||||
Bidirectional `MANY_TO_ONE` / `ONE_TO_MANY` connections between objects.
|
||||
<Card title="关系" icon="diagram-project" href="/l/zh/developers/extend/apps/data/relations">
|
||||
对象之间的双向 `MANY_TO_ONE` / `ONE_TO_MANY` 连接。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Entities at a glance
|
||||
## 实体一览
|
||||
|
||||
| Entity | Purpose | Defined with |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
||||
| **Object** | A new custom record type (e.g. PostCard, Invoice) with its own fields | `defineObject()` |
|
||||
| **Field** | A column on an object. Standalone fields can extend objects you didn't create (e.g. add `loyaltyTier` to Company) | `defineField()` |
|
||||
| **Relation** | A bidirectional link between two objects — both sides declared as fields | `defineField()` with `FieldType.RELATION` |
|
||||
| 实体 | 目的 | 定义方式 |
|
||||
| ------ | ----------------------------------------------------- | ------------------------------------------- |
|
||||
| **对象** | 具有自有字段的新自定义记录类型(例如 PostCard、Invoice) | `defineObject()` |
|
||||
| **字段** | 对象上的一列。 独立字段可以扩展你未创建的对象(例如向 Company 添加 `loyaltyTier`) | `defineField()` |
|
||||
| **关系** | 两个对象之间的双向链接——双方都声明为字段 | 使用 `defineField()` 并指定 `FieldType.RELATION` |
|
||||
|
||||
The SDK detects these via AST analysis at build time, so file organization is up to you — the convention is `src/objects/` and `src/fields/`. Stable `universalIdentifier` UUIDs tie everything together across deploys.
|
||||
SDK 会在构建时通过 AST 分析检测这些内容,因此文件组织方式由你决定——约定是使用 `src/objects/` 和 `src/fields/`。 稳定的 `universalIdentifier` UUID 在不同部署之间将一切关联在一起。
|
||||
|
||||
<Note>
|
||||
Looking for **Application Config** or **Roles & Permissions**? Those describe the app itself rather than the data it adds — they live under [Config](/l/zh/developers/extend/apps/config/overview). Looking for **Connections** (Linear, GitHub, Slack OAuth)? Those exist to be called *from* logic functions and live under [Logic](/l/zh/developers/extend/apps/logic/connections).
|
||||
在找 **Application Config** 或 **Roles & Permissions** 吗? 这些描述的是应用本身而不是它添加的数据——相关内容位于 [Config](/l/zh/developers/extend/apps/config/overview) 下。 在找 **Connections**(Linear、GitHub、Slack OAuth)吗? 这些用于*从*逻辑函数中调用,并位于 [Logic](/l/zh/developers/extend/apps/logic/connections) 下。
|
||||
</Note>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
---
|
||||
title: 关系
|
||||
description: Connect objects together with bidirectional MANY_TO_ONE / ONE_TO_MANY relations.
|
||||
description: 使用双向 MANY_TO_ONE / ONE_TO_MANY 关系将对象连接在一起。
|
||||
icon: diagram-project
|
||||
---
|
||||
|
||||
Relations connect two objects together. In Twenty, relations are always **bidirectional** — every relation has two sides, and each side is declared as a field that references the other.
|
||||
关系用于将两个对象连接在一起。 在 Twenty 中,关系始终是 **双向的**——每个关系都有两侧,每一侧都作为一个字段声明,并引用另一侧。
|
||||
|
||||
| 关系类型 | 描述 | 是否有外键? |
|
||||
| ------------- | ------------------- | --------------------- |
|
||||
| `MANY_TO_ONE` | 该对象的多条记录指向目标对象的一条记录 | 是(`joinColumnName`) |
|
||||
| `ONE_TO_MANY` | 该对象的一条记录拥有目标对象的多条记录 | No (the inverse side) |
|
||||
| 关系类型 | 描述 | 是否有外键? |
|
||||
| ------------- | ------------------- | ------------------- |
|
||||
| `MANY_TO_ONE` | 该对象的多条记录指向目标对象的一条记录 | 是(`joinColumnName`) |
|
||||
| `ONE_TO_MANY` | 该对象的一条记录拥有目标对象的多条记录 | 否(反向侧) |
|
||||
|
||||
## How relations work
|
||||
## 关系如何工作
|
||||
|
||||
Every relation requires **two fields** that reference each other:
|
||||
每个关系都需要两个相互引用的字段:
|
||||
|
||||
1. The **MANY_TO_ONE** side — lives on the object that holds the foreign key.
|
||||
2. The **ONE_TO_MANY** side — lives on the object that owns the collection.
|
||||
1. **MANY_TO_ONE** 侧——位于持有外键的对象上。
|
||||
2. **ONE_TO_MANY** 侧——位于拥有集合的对象上。
|
||||
|
||||
Both fields use `FieldType.RELATION` and cross-reference each other via `relationTargetFieldMetadataUniversalIdentifier`.
|
||||
两个字段都使用 `FieldType.RELATION`,并通过 `relationTargetFieldMetadataUniversalIdentifier` 相互交叉引用。
|
||||
|
||||
## Example: Post Card has many Recipients
|
||||
## 示例:Post Card 拥有多个收件人
|
||||
|
||||
A `PostCard` can be sent to many `PostCardRecipient` records. Each recipient belongs to exactly one post card.
|
||||
一个 `PostCard` 可以发送给多个 `PostCardRecipient` 记录。 每个收件人只隶属于一张 PostCard。
|
||||
|
||||
**Step 1: Define the ONE_TO_MANY side on PostCard** (the "one" side):
|
||||
**步骤 1:在 PostCard 上定义 ONE_TO_MANY 侧**(“一”侧):
|
||||
|
||||
```ts src/fields/post-card-recipients-on-post-card.field.ts
|
||||
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
|
||||
@@ -51,7 +51,7 @@ export default defineField({
|
||||
});
|
||||
```
|
||||
|
||||
**Step 2: Define the MANY_TO_ONE side on PostCardRecipient** (the "many" side — holds the foreign key):
|
||||
**步骤 2:在 PostCardRecipient 上定义 MANY_TO_ONE 侧**(“多”侧——持有外键):
|
||||
|
||||
```ts src/fields/post-card-on-post-card-recipient.field.ts
|
||||
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
|
||||
@@ -81,12 +81,12 @@ export default defineField({
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Circular imports:** both relation fields reference each other's `universalIdentifier`. To avoid circular import issues, export your field IDs as named constants from each file and import them in the other. The build system resolves these at compile time.
|
||||
\*\*循环导入:\*\*两个关系字段相互引用对方的 `universalIdentifier`。 为避免循环导入问题,请在各自文件中将字段 ID 作为具名常量导出,并在另一个文件中导入它们。 构建系统会在编译时解析这些引用。
|
||||
</Note>
|
||||
|
||||
## Relating to standard objects
|
||||
## 与标准对象建立关系
|
||||
|
||||
To create a relation with a built-in Twenty object (Person, Company, etc.), use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
|
||||
要与内置的 Twenty 对象(Person、Company 等)建立关系,请使用 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
|
||||
|
||||
```ts src/fields/person-on-self-hosting-user.field.ts
|
||||
import {
|
||||
@@ -120,20 +120,20 @@ export default defineField({
|
||||
});
|
||||
```
|
||||
|
||||
## Relation field properties
|
||||
## 关系字段属性
|
||||
|
||||
| Property | Required | Description |
|
||||
| ------------------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `type` | Yes | Must be `FieldType.RELATION` |
|
||||
| `relationTargetObjectMetadataUniversalIdentifier` | Yes | The `universalIdentifier` of the target object |
|
||||
| `relationTargetFieldMetadataUniversalIdentifier` | Yes | The `universalIdentifier` of the matching field on the target object |
|
||||
| `universalSettings.relationType` | Yes | `RelationType.MANY_TO_ONE` or `RelationType.ONE_TO_MANY` |
|
||||
| `universalSettings.onDelete` | MANY_TO_ONE only | What happens when the referenced record is deleted: `CASCADE`, `SET_NULL`, `RESTRICT`, or `NO_ACTION` |
|
||||
| `universalSettings.joinColumnName` | MANY_TO_ONE only | Database column name for the foreign key (e.g., `postCardId`) |
|
||||
| 属性 | 必填 | 描述 |
|
||||
| ------------------------------------------------- | ---------------- | -------------------------------------------------------------- |
|
||||
| `type` | 是 | 必须为 `FieldType.RELATION` |
|
||||
| `relationTargetObjectMetadataUniversalIdentifier` | 是 | 目标对象的 `universalIdentifier` |
|
||||
| `relationTargetFieldMetadataUniversalIdentifier` | 是 | 目标对象上匹配字段的 `universalIdentifier` |
|
||||
| `universalSettings.relationType` | 是 | `RelationType.MANY_TO_ONE` 或 `RelationType.ONE_TO_MANY` |
|
||||
| `universalSettings.onDelete` | 仅适用于 MANY_TO_ONE | 当被引用的记录被删除时的处理方式:`CASCADE`、`SET_NULL`、`RESTRICT` 或 `NO_ACTION` |
|
||||
| `universalSettings.joinColumnName` | 仅适用于 MANY_TO_ONE | 外键的数据库列名(例如,`postCardId`) |
|
||||
|
||||
## Inline relation fields
|
||||
## 内联关系字段
|
||||
|
||||
You can also declare a relation directly inside [`defineObject`](/l/zh/developers/extend/apps/data/objects). When inline, omit `objectUniversalIdentifier` — it's inherited from the parent object:
|
||||
你也可以直接在 [`defineObject`](/l/zh/developers/extend/apps/data/objects) 中声明关系。 以内联方式声明时,省略 `objectUniversalIdentifier`——它会从父对象继承:
|
||||
|
||||
```ts
|
||||
export default defineObject({
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: Concepts
|
||||
description: How Twenty apps work — entity model, sandboxing, and the install lifecycle.
|
||||
title: 概念
|
||||
description: Twenty 应用的工作方式 — 实体模型、沙盒机制和安装生命周期。
|
||||
icon: sitemap
|
||||
---
|
||||
|
||||
Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls.
|
||||
Twenty 应用是 TypeScript 包,可通过自定义对象、逻辑、UI 组件和 AI 能力扩展你的工作区。 它们在 Twenty 平台上运行,具备完备的沙盒与权限控制。
|
||||
|
||||
## How apps work
|
||||
## 应用如何运作
|
||||
|
||||
An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. These functions validate your configuration at build time and provide IDE autocompletion and type safety.
|
||||
应用是由 `twenty-sdk` 包中的 `defineEntity()` 函数声明的**实体**集合。 SDK 在构建时通过 AST 分析检测到这些声明,并生成一份**清单**——完整描述你的应用为工作区新增的内容。 这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。
|
||||
|
||||
```
|
||||
your-app/
|
||||
@@ -29,35 +29,35 @@ your-app/
|
||||
```
|
||||
|
||||
<Note>
|
||||
**File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement.
|
||||
**文件组织由你决定。** 实体检测基于 AST——无论文件位于何处,SDK 都能找到 `export default defineEntity(...)` 的调用。 上述文件夹结构是一种约定,而非强制要求。
|
||||
</Note>
|
||||
|
||||
## Entity types
|
||||
## 实体类型
|
||||
|
||||
| Entity | Purpose | Docs |
|
||||
| ------------------------ | ------------------------------------------ | ----------------------------------------------------------------------------- |
|
||||
| **Application** | App identity, default role, variables | [Application Config](/l/zh/developers/extend/apps/config/application) |
|
||||
| **Role** | Permission sets on objects and fields | [Roles & Permissions](/l/zh/developers/extend/apps/config/roles) |
|
||||
| **Object** | Custom record types with fields | [Objects](/l/zh/developers/extend/apps/data/objects) |
|
||||
| **Field** | Add fields to objects from other apps | [Extending Objects](/l/zh/developers/extend/apps/data/extending-objects) |
|
||||
| **Relation** | Bidirectional links between objects | [Relations](/l/zh/developers/extend/apps/data/relations) |
|
||||
| **Logic Function** | Server-side TypeScript with triggers | [Logic Functions](/l/zh/developers/extend/apps/logic/logic-functions) |
|
||||
| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/zh/developers/extend/apps/logic/skills-and-agents) |
|
||||
| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/zh/developers/extend/apps/logic/skills-and-agents) |
|
||||
| **Connection Provider** | OAuth credentials for third-party APIs | [Connections](/l/zh/developers/extend/apps/logic/connections) |
|
||||
| **View** | Pre-configured record list views | [Views](/l/zh/developers/extend/apps/layout/views) |
|
||||
| **Navigation Menu Item** | Custom sidebar entries | [Navigation Menu Items](/l/zh/developers/extend/apps/layout/navigation-menu-items) |
|
||||
| **Page Layout** | Tabs and widgets on a record's detail page | [Page Layouts](/l/zh/developers/extend/apps/layout/page-layouts) |
|
||||
| **Front Component** | Sandboxed React UI inside Twenty | [Front Components](/l/zh/developers/extend/apps/layout/front-components) |
|
||||
| **Command Menu Item** | Quick actions and Cmd+K entries | [Command Menu Items](/l/zh/developers/extend/apps/layout/command-menu-items) |
|
||||
| 实体 | 目的 | 文档 |
|
||||
| --------- | --------------------- | ---------------------------------------------------------------- |
|
||||
| **应用程序** | 应用标识、默认角色、变量 | [Application Config](/l/zh/developers/extend/apps/config/application) |
|
||||
| **角色** | 对象和字段的权限集 | [角色和权限](/l/zh/developers/extend/apps/config/roles) |
|
||||
| **对象** | 带字段的自定义记录类型 | [对象](/l/zh/developers/extend/apps/data/objects) |
|
||||
| **字段** | 从其他应用向对象添加字段 | [扩展对象](/l/zh/developers/extend/apps/data/extending-objects) |
|
||||
| **关系** | 对象之间的双向链接 | [关系](/l/zh/developers/extend/apps/data/relations) |
|
||||
| **逻辑函数** | 带触发器的服务端 TypeScript | [逻辑函数](/l/zh/developers/extend/apps/logic/logic-functions) |
|
||||
| **技能** | 可复用的 AI 代理指令 | [技能与代理](/l/zh/developers/extend/apps/logic/skills-and-agents) |
|
||||
| **代理** | 具有自定义提示词的 AI 助手 | [技能与代理](/l/zh/developers/extend/apps/logic/skills-and-agents) |
|
||||
| **连接提供方** | 用于第三方 API 的 OAuth 凭证 | [连接](/l/zh/developers/extend/apps/logic/connections) |
|
||||
| **视图** | 预配置的记录列表视图 | [视图](/l/zh/developers/extend/apps/layout/views) |
|
||||
| **导航菜单项** | 自定义侧边栏条目 | [导航菜单项](/l/zh/developers/extend/apps/layout/navigation-menu-items) |
|
||||
| **页面布局** | 记录详情页上的选项卡和小部件 | [页面布局](/l/zh/developers/extend/apps/layout/page-layouts) |
|
||||
| **前端组件** | Twenty 内的沙盒化 React UI | [前端组件](/l/zh/developers/extend/apps/layout/front-components) |
|
||||
| **命令菜单项** | 快速操作和 Cmd+K 项 | [命令菜单项](/l/zh/developers/extend/apps/layout/command-menu-items) |
|
||||
|
||||
## Sandboxing
|
||||
## 沙盒化
|
||||
|
||||
* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions.
|
||||
* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API.
|
||||
* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`.
|
||||
* **逻辑函数** 在服务器上的独立 Node.js 进程中运行。 它们只能通过类型化的 API 客户端访问数据,且范围受应用角色权限限制。
|
||||
* **前端组件** 在使用 Remote DOM 的 Web Worker 中运行——与主页面沙盒隔离,但渲染原生 DOM 元素(非 iframe)。 它们通过消息传递的宿主 API 与 Twenty 通信。
|
||||
* **权限** 在 API 层面强制执行。 运行时令牌(`TWENTY_APP_ACCESS_TOKEN`)源自 `defineApplication()` 中定义的角色。
|
||||
|
||||
## App lifecycle
|
||||
## 应用生命周期
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
@@ -76,26 +76,26 @@ your-app/
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes.
|
||||
* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest.
|
||||
* **Pre/post-install hooks** — optional functions that run during installation. See [Install Hooks](/l/zh/developers/extend/apps/config/install-hooks) for details.
|
||||
* **`yarn twenty dev`** — 监视你的源文件,并将更改实时同步到已连接的 Twenty 服务器。 当模式发生变化时,会自动重新生成类型化的 API 客户端。
|
||||
* **`yarn twenty build`** — 编译 TypeScript,使用 esbuild 打包逻辑函数和前端组件,并生成清单。
|
||||
* **预/后安装钩子** — 在安装过程中运行的可选函数。 详见 [安装钩子](/l/zh/developers/extend/apps/config/install-hooks)。
|
||||
|
||||
## Next steps
|
||||
## 后续步骤
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Config" icon="screwdriver-wrench" href="/l/zh/developers/extend/apps/config/overview">
|
||||
Application identity, default role, and install hooks.
|
||||
<Card title="配置" icon="screwdriver-wrench" href="/l/zh/developers/extend/apps/config/overview">
|
||||
应用标识、默认角色和安装钩子。
|
||||
</Card>
|
||||
<Card title="Data" icon="database" href="/l/zh/developers/extend/apps/data/overview">
|
||||
Objects, fields, and bidirectional relations.
|
||||
<Card title="数据" icon="database" href="/l/zh/developers/extend/apps/data/overview">
|
||||
对象、字段和双向关系。
|
||||
</Card>
|
||||
<Card title="Logic" icon="bolt" href="/l/zh/developers/extend/apps/logic/overview">
|
||||
Logic functions, skills, agents, and OAuth connections.
|
||||
<Card title="逻辑" icon="bolt" href="/l/zh/developers/extend/apps/logic/overview">
|
||||
逻辑函数、技能、代理和 OAuth 连接。
|
||||
</Card>
|
||||
<Card title="Layout" icon="table-columns" href="/l/zh/developers/extend/apps/layout/overview">
|
||||
Views, navigation, page layouts, front components.
|
||||
<Card title="布局" icon="table-columns" href="/l/zh/developers/extend/apps/layout/overview">
|
||||
视图、导航、页面布局、前端组件。
|
||||
</Card>
|
||||
<Card title="Operations" icon="rocket" href="/l/zh/developers/extend/apps/operations/overview">
|
||||
CLI, testing, remotes, CI, and publishing your app.
|
||||
<Card title="操作" icon="rocket" href="/l/zh/developers/extend/apps/operations/overview">
|
||||
CLI、测试、远程、CI,以及发布你的应用。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
---
|
||||
title: Local Server
|
||||
description: Manage the local Twenty Docker server — start, stop, upgrade, parallel test instance, and manual SDK setup.
|
||||
title: 本地服务器
|
||||
description: 管理本地 Twenty Docker 服务器 — 启动、停止、升级、并行测试实例,以及手动设置 SDK。
|
||||
icon: server
|
||||
---
|
||||
|
||||
## Managing the local server
|
||||
## 管理本地服务器
|
||||
|
||||
Use `yarn twenty server` to control the local Twenty container:
|
||||
使用 `yarn twenty server` 控制本地的 Twenty 容器:
|
||||
|
||||
| Command | What it does |
|
||||
| -------------------------------------- | -------------------------------------------- |
|
||||
| `yarn twenty server start` | Start the server (pulls the image if needed) |
|
||||
| `yarn twenty server start --port 3030` | Start on a custom port |
|
||||
| `yarn twenty server stop` | Stop the server (preserves data) |
|
||||
| `yarn twenty server status` | Show URL, version, and login credentials |
|
||||
| `yarn twenty server logs` | Stream server logs |
|
||||
| `yarn twenty server reset` | Wipe data and start fresh |
|
||||
| `yarn twenty server upgrade` | Pull the latest `twenty-app-dev` image |
|
||||
| `yarn twenty server upgrade 2.2.0` | Upgrade to a specific version |
|
||||
| 命令 | 作用 |
|
||||
| -------------------------------------- | ------------------------- |
|
||||
| `yarn twenty server start` | 启动服务器(按需拉取镜像) |
|
||||
| `yarn twenty server start --port 3030` | 在自定义端口启动 |
|
||||
| `yarn twenty server stop` | 停止服务器(保留数据) |
|
||||
| `yarn twenty server status` | 显示 URL、版本和登录凭据 |
|
||||
| `yarn twenty server logs` | 流式输出服务器日志 |
|
||||
| `yarn twenty server reset` | 清空数据并全新开始 |
|
||||
| `yarn twenty server upgrade` | 拉取最新的 `twenty-app-dev` 镜像 |
|
||||
| `yarn twenty server upgrade 2.2.0` | 升级到指定版本 |
|
||||
|
||||
Data persists across restarts in two Docker volumes (`twenty-app-dev-data` for PostgreSQL, `twenty-app-dev-storage` for files). Use `reset` to wipe everything.
|
||||
数据在重启后会保留,存储于两个 Docker 卷中(`twenty-app-dev-data` 用于 PostgreSQL,`twenty-app-dev-storage` 用于文件)。 使用 `reset` 清空所有内容。
|
||||
|
||||
## Upgrading the server image
|
||||
## 升级服务器镜像
|
||||
|
||||
`yarn twenty server upgrade` pulls the latest image, compares digests, and only recreates the container if anything actually changed. Volumes are preserved — only the container is replaced. If a new image was pulled and the container was running, the upgrade automatically starts a new container; run `yarn twenty server start` afterward to wait for it to become healthy.
|
||||
`yarn twenty server upgrade` 将拉取最新镜像、比较摘要,并且仅在确有变更时才重新创建容器。 数据卷将被保留——只会替换容器。 如果已拉取新镜像且容器正在运行,升级会自动启动一个新容器;之后运行 `yarn twenty server start` 以等待其变为健康状态。
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server upgrade # Latest
|
||||
yarn twenty server upgrade 2.2.0 # Specific version
|
||||
```
|
||||
|
||||
Verify the running version with `yarn twenty server status` (it shows the `APP_VERSION` baked into the container).
|
||||
使用 `yarn twenty server status` 验证正在运行的版本(它会显示写入容器的 `APP_VERSION`)。
|
||||
|
||||
## Running a parallel test instance
|
||||
## 运行并行测试实例
|
||||
|
||||
Pass `--test` to any `server` command to manage a second, fully isolated instance — useful for integration tests or experiments without touching your main dev data:
|
||||
向任意 `server` 命令传递 `--test` 以管理第二个、完全隔离的实例——这有助于在不影响主开发数据的情况下进行集成测试或试验:
|
||||
|
||||
| Command | What it does |
|
||||
| ----------------------------------- | ----------------------------------------------- |
|
||||
| `yarn twenty server start --test` | Start the test instance (defaults to port 2021) |
|
||||
| `yarn twenty server stop --test` | Stop it |
|
||||
| `yarn twenty server status --test` | Show its status |
|
||||
| `yarn twenty server logs --test` | Stream its logs |
|
||||
| `yarn twenty server reset --test` | Wipe its data |
|
||||
| `yarn twenty server upgrade --test` | Upgrade its image |
|
||||
| 命令 | 作用 |
|
||||
| ----------------------------------- | ------------------- |
|
||||
| `yarn twenty server start --test` | 启动测试实例 (默认端口为 2021) |
|
||||
| `yarn twenty server stop --test` | 停止它 |
|
||||
| `yarn twenty server status --test` | 显示其状态 |
|
||||
| `yarn twenty server logs --test` | 流式输出其日志 |
|
||||
| `yarn twenty server reset --test` | 清空其数据 |
|
||||
| `yarn twenty server upgrade --test` | 升级其镜像 |
|
||||
|
||||
The test instance has its own container (`twenty-app-dev-test`), volumes (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`), and config — it runs alongside your main instance without conflicts. Combine `--test` with `--port` to override 2021.
|
||||
测试实例有其自己的容器(`twenty-app-dev-test`)、卷(`twenty-app-dev-test-data`、`twenty-app-dev-test-storage`)和配置——它可与你的主实例并行运行且不会发生冲突。 将 `--test` 与 `--port` 组合使用以覆盖 2021 端口。
|
||||
|
||||
## Manual setup (without the scaffolder)
|
||||
## 手动设置(不使用脚手架)
|
||||
|
||||
Skip the scaffolder if you're adding the SDK to an existing project:
|
||||
如果你要将 SDK 添加到现有项目中,可跳过脚手架:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Add the script to `package.json`:
|
||||
在 `package.json` 中添加该脚本:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -65,8 +65,8 @@ Add the script to `package.json`:
|
||||
}
|
||||
```
|
||||
|
||||
You can now run `yarn twenty dev`, `yarn twenty server start`, and the rest.
|
||||
现在你可以运行 `yarn twenty dev`、`yarn twenty server start`,以及其他命令。
|
||||
|
||||
<Note>
|
||||
Don't install `twenty-sdk` globally — pin it per project so each app uses its own version.
|
||||
不要全局安装 `twenty-sdk` —— 在每个项目中固定其版本,使每个应用都使用各自的版本。
|
||||
</Note>
|
||||
|
||||
+12
-12
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Project Structure
|
||||
description: What's inside a scaffolded Twenty app — files, folders, and what each one does.
|
||||
title: 项目结构
|
||||
description: 脚手架生成的 Twenty 应用中包含哪些内容——文件、文件夹以及它们各自的作用。
|
||||
icon: folder-tree
|
||||
---
|
||||
|
||||
A new app generated by `npx create-twenty-app` looks like this:
|
||||
由 `npx create-twenty-app` 生成的新应用看起来是这样的:
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -25,16 +25,16 @@ my-twenty-app/
|
||||
README.md, LLMS.md
|
||||
```
|
||||
|
||||
## Key files
|
||||
## 关键文件
|
||||
|
||||
| File / Folder | Purpose |
|
||||
| ---------------------------------------- | -------------------------------------------------------------- |
|
||||
| `src/application-config.ts` | **Required.** The main configuration file for your app. |
|
||||
| `src/default-role.ts` | Default role controlling what your logic functions can access. |
|
||||
| `src/constants/universal-identifiers.ts` | Auto-generated UUIDs and metadata (display name, description). |
|
||||
| `src/__tests__/` | Integration tests (setup + example test). |
|
||||
| `public/` | Static assets (images, fonts) served with your app. |
|
||||
| 文件 / 文件夹 | 目的 |
|
||||
| ---------------------------------------- | ------------------------- |
|
||||
| `src/application-config.ts` | **必需。** 应用的主配置文件。 |
|
||||
| `src/default-role.ts` | 默认角色,用于控制你的逻辑函数可访问的内容。 |
|
||||
| `src/constants/universal-identifiers.ts` | 自动生成的 UUID 和元数据(显示名称、描述)。 |
|
||||
| `src/__tests__/` | 集成测试(设置 + 示例测试)。 |
|
||||
| `public/` | 随应用一起提供的静态资源(图像、字体)。 |
|
||||
|
||||
<Note>
|
||||
**File organization is up to you.** The folders above are conventions — the SDK detects entities via AST analysis on `export default defineEntity(...)` calls regardless of where the file lives.
|
||||
**文件组织由你决定。** 上述文件夹只是约定——SDK 通过对 `export default defineEntity(...)` 调用进行 AST 分析来检测实体,而不受文件所在位置影响。
|
||||
</Note>
|
||||
|
||||
@@ -1,184 +1,184 @@
|
||||
---
|
||||
title: Quick Start
|
||||
title: 快速开始
|
||||
icon: rocket
|
||||
description: Create your first Twenty app in minutes.
|
||||
description: 几分钟内创建你的第一个 Twenty 应用。
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
## 先决条件
|
||||
|
||||
* **Node.js 24+** — [Download](https://nodejs.org/)
|
||||
* **Yarn 4** — bundled with Node via Corepack. Enable it: `corepack enable`
|
||||
* **Docker** — [Download](https://www.docker.com/products/docker-desktop/). Needed to run a local Twenty server. Skip if you already have Twenty running elsewhere.
|
||||
* **Node.js 24+** — [在此下载](https://nodejs.org/)
|
||||
* **Yarn 4** — 通过 Corepack 随 Node.js 提供。 启用它:`corepack enable`
|
||||
* **Docker** — [在此下载](https://www.docker.com/products/docker-desktop/)。 运行本地 Twenty 服务器所需。 如果你已经在其他地方运行了 Twenty,请跳过。
|
||||
|
||||
Building a Twenty app has three phases. The scaffolder collapses them into one happy-path command, but each phase is a separate concept — when something fails, knowing which phase you're in tells you what to fix.
|
||||
构建一个 Twenty 应用包含三个阶段。 脚手架工具将它们合并为一个理想路径的命令,但每个阶段都是独立的概念——当出现问题时,知道自己处于哪个阶段可以指明需要修复什么。
|
||||
|
||||
| Phase | What you do | Tool | Result |
|
||||
| ------------------- | ---------------------------------- | ----------------------------- | ----------------------------- |
|
||||
| **1. Scaffold** | Generate the app's source code | `npx create-twenty-app` | A TypeScript project on disk |
|
||||
| **2. Run a server** | Start a Twenty server to sync into | Docker + `yarn twenty server` | A running Twenty instance |
|
||||
| **3. Sync** | Live-sync your code to the server | `yarn twenty dev` | Your changes appear in the UI |
|
||||
| 阶段 | 你要做什么 | 工具 | 结果 |
|
||||
| ------------ | -------------------- | ----------------------------- | -------------------- |
|
||||
| **1. 脚手架** | 生成应用的源代码 | `npx create-twenty-app` | 磁盘上的一个 TypeScript 项目 |
|
||||
| **2. 运行服务器** | 启动一个 Twenty 服务器以进行同步 | Docker + `yarn twenty server` | 一个正在运行的 Twenty 实例 |
|
||||
| **3. 同步** | 将你的代码实时同步到服务器 | `yarn twenty dev` | 你的更改会出现在 UI 中 |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Scaffold your project
|
||||
## 阶段 1 — 搭建项目脚手架
|
||||
|
||||
Create a new app from the template:
|
||||
从模板创建一个新应用:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
```
|
||||
|
||||
You'll be prompted for a name and description — press **Enter** for the defaults. This generates a TypeScript project in `my-twenty-app/` with a starter `application-config.ts`, a default role, a CI workflow, and an integration test.
|
||||
系统会提示你输入名称和描述——按下 **Enter** 采用默认值。 这将在 `my-twenty-app/` 中生成一个 TypeScript 项目,包含一个入门版的 `application-config.ts`、一个默认角色、一个 CI 工作流,以及一个集成测试。
|
||||
|
||||
**After this phase:** you have an app's source code on your machine. It isn't running yet — that's Phase 2.
|
||||
**完成此阶段后:** 你的机器上已有该应用的源代码。 它还未运行——那是第 2 阶段的内容。
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Run a local Twenty server
|
||||
## 阶段 2 — 运行本地 Twenty 服务器
|
||||
|
||||
Your app needs a Twenty server to sync into. The server is a full Twenty instance — UI, GraphQL API, PostgreSQL — running locally in Docker. Your local code uploads its definitions to that server, which makes them appear in the UI.
|
||||
你的应用需要一个 Twenty 服务器来进行同步。 该服务器是一个完整的 Twenty 实例——包含 UI、GraphQL API、PostgreSQL——在本地的 Docker 中运行。 你的本地代码会将其定义上传到该服务器,从而使其显示在 UI 中。
|
||||
|
||||
The scaffolder offers to start one for you:
|
||||
脚手架工具会为你提供启动它的选项:
|
||||
|
||||
> **Would you like to set up a local Twenty instance?**
|
||||
> **是否要设置本地 Twenty 实例?**
|
||||
|
||||
* **Yes (recommended)** — pulls the `twentycrm/twenty-app-dev` Docker image and starts it on port `2020`. Make sure Docker is running first.
|
||||
* **No** — choose this if you already have a Twenty server you want to connect to. You can wire it up later with `yarn twenty remote add`.
|
||||
* **是(推荐)** — 将拉取 `twentycrm/twenty-app-dev` Docker 镜像,并在端口 `2020` 上启动它。 请先确保 Docker 正在运行。
|
||||
* **否** — 如果你已经有一个想要连接的 Twenty 服务器,请选择此项。 你可以稍后通过 `yarn twenty remote add` 将其连接起来。
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Should start local instance?" />
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="是否启动本地实例?" />
|
||||
</div>
|
||||
|
||||
Once the server is up, a browser opens for sign-in. Use the pre-seeded demo account:
|
||||
服务器启动后,浏览器会打开登录页面。 使用预置的演示账户:
|
||||
|
||||
* **Email:** `tim@apple.dev`
|
||||
* **Password:** `tim@apple.dev`
|
||||
* **邮箱:** `tim@apple.dev`
|
||||
* **密码:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty login screen" />
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty 登录界面" />
|
||||
</div>
|
||||
|
||||
Click **Authorize** on the next screen — this gives the CLI access to your workspace.
|
||||
在下一屏点击 **Authorize** —— 这将授予 CLI 访问你工作区的权限。
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI authorization screen" />
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI 授权界面" />
|
||||
</div>
|
||||
|
||||
Your terminal will confirm everything is set up.
|
||||
你的终端会确认一切已就绪。
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App scaffolded successfully" />
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="应用脚手架创建成功" />
|
||||
</div>
|
||||
|
||||
**After this phase:** you have a running Twenty server at [http://localhost:2020](http://localhost:2020) with your CLI authorized to sync to it.
|
||||
**完成此阶段后:** 你在 [http://localhost:2020](http://localhost:2020) 上拥有一个正在运行的 Twenty 服务器,且你的 CLI 已获授权可与其同步。
|
||||
|
||||
<Note>
|
||||
If Docker isn't installed or running, the scaffolder will tell you the right start command for your OS. Once Docker is up, you can resume with `yarn twenty server start` — no need to re-scaffold.
|
||||
如果未安装或未运行 Docker,脚手架工具会告诉你在所用操作系统上正确的启动命令。 Docker 启动后,你可以通过 `yarn twenty server start` 继续——无需重新生成脚手架。
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Sync your changes
|
||||
## 阶段 3 — 同步你的更改
|
||||
|
||||
This is the inner loop you'll spend most of your time in.
|
||||
这是你大部分时间所处的内循环。
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
This watches `src/`, rebuilds on every change, and syncs the result to the server. Edit a file, save, and within a second the server reflects the change. You'll see a live status panel in your terminal.
|
||||
它会监视 `src/`,在每次更改时重新构建,并将结果同步到服务器。 编辑文件、保存,服务器会在一秒内反映出更改。 你会在终端中看到一个实时状态面板。
|
||||
|
||||
For more detailed output (build logs, sync requests, error traces), add `--verbose`.
|
||||
如需更详细的输出(构建日志、同步请求、错误跟踪),请添加 `--verbose`。
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.png" alt="Dev mode terminal output" />
|
||||
<img src="/images/docs/developers/extends/apps/dev.png" alt="开发模式终端输出" />
|
||||
</div>
|
||||
|
||||
Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer). You should see your app under **Your Apps**.
|
||||
打开 [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer)。 你应当在 **你的应用** 下看到你的应用。
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Your Apps list showing My twenty app" />
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="“你的应用”列表显示 My twenty app" />
|
||||
</div>
|
||||
|
||||
Click **My twenty app** to see its **application registration** — a server-level record describing your app (name, identifier, OAuth credentials, source). One registration can be installed across multiple workspaces on the same server.
|
||||
点击 **My twenty app** 查看其**应用注册**——一条用于描述你的应用(名称、标识符、OAuth 凭据、来源)的服务器级记录。 同一服务器上的多个工作区可以安装同一个注册项。
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Application registration details" />
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="应用注册详情" />
|
||||
</div>
|
||||
|
||||
Click **View installed app** to see the workspace install. The **About** tab shows version and management options.
|
||||
点击 **查看已安装的应用** 以查看工作区安装项。 **关于** 选项卡显示版本和管理选项。
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installed app" />
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="已安装的应用" />
|
||||
</div>
|
||||
|
||||
**After this phase:** you have a live development loop. Edit any file in `src/` and it appears in the UI.
|
||||
**完成此阶段后:** 你拥有一个实时的开发循环。 编辑 `src/` 中的任意文件,更改会显示在 UI 中。
|
||||
|
||||
### One-shot sync for CI and scripts
|
||||
### 用于 CI 和脚本的一次性同步
|
||||
|
||||
Pass `--once` to run a single build + sync and exit — same pipeline, no watcher:
|
||||
传入 `--once` 以执行一次构建与同步后退出——相同的流水线,无文件监视器:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty dev --once
|
||||
```
|
||||
|
||||
| Command | Behavior | When to use |
|
||||
| ------------------------ | ------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| `yarn twenty dev` | Watches and re-syncs on every change. Runs until you stop it. | Interactive local development. |
|
||||
| `yarn twenty dev --once` | Single build + sync, exits `0` on success, `1` on failure. | CI, pre-commit hooks, AI agents, scripted workflows. |
|
||||
| 命令 | 行为 | 适用场景 |
|
||||
| ------------------------ | -------------------------------- | ------------------------------ |
|
||||
| `yarn twenty dev` | 监视并在每次更改时重新同步。 持续运行,直到你将其停止。 | 交互式本地开发。 |
|
||||
| `yarn twenty dev --once` | 单次构建与同步,成功时以 `0` 退出,失败时以 `1` 退出。 | CI、pre-commit 钩子、AI 代理、脚本化工作流。 |
|
||||
|
||||
Both modes need a server in development mode and an authenticated remote.
|
||||
两种模式都需要处于开发模式的服务器和已认证的远程。
|
||||
|
||||
<Warning>
|
||||
Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests — use `yarn twenty deploy` to deploy to production servers. See [Publishing](/l/zh/developers/extend/apps/operations/publishing).
|
||||
开发模式仅适用于以开发模式运行的 Twenty 实例(`NODE_ENV=development`)。 生产实例会拒绝开发同步请求——请使用 `yarn twenty deploy` 部署到生产服务器。 参见[发布](/l/zh/developers/extend/apps/operations/publishing)。
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Starting from an example
|
||||
## 从示例开始
|
||||
|
||||
Use `--example` to start with a more complete project (custom objects, fields, logic functions, front components):
|
||||
使用 `--example` 从一个更完整的项目开始(自定义对象、字段、逻辑函数、前端组件):
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx create-twenty-app@latest my-twenty-app --example postcard
|
||||
```
|
||||
|
||||
Examples live in [twenty-apps/examples](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples). You can also scaffold individual entities into an existing project with `yarn twenty add` — see [Scaffolding](/l/zh/developers/extend/apps/getting-started/scaffolding).
|
||||
示例位于 [twenty-apps/examples](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples)。 你也可以使用 `yarn twenty add` 为现有项目生成单个实体的脚手架——参见[脚手架](/l/zh/developers/extend/apps/getting-started/scaffolding)。
|
||||
|
||||
---
|
||||
|
||||
## What you can build
|
||||
## 你可以构建的内容
|
||||
|
||||
Apps are composed of **entities** — each defined as a TypeScript file with a single `export default`:
|
||||
应用由**实体**组成——每个实体定义为一个包含单一 `export default` 的 TypeScript 文件:
|
||||
|
||||
| Entity | What it does |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------- |
|
||||
| **Objects & Fields** | Custom data models (Post Card, Invoice, etc.) with typed fields |
|
||||
| **Logic functions** | Server-side TypeScript triggered by HTTP routes, cron schedules, or database events |
|
||||
| **Front components** | React components that render inside Twenty's UI (side panel, widgets, command menu) |
|
||||
| **Skills & Agents** | AI capabilities — reusable instructions and autonomous assistants |
|
||||
| **Views & Navigation** | Pre-configured list views and sidebar menu items |
|
||||
| **Page layouts** | Custom record detail pages with tabs and widgets |
|
||||
| 实体 | 作用 |
|
||||
| ---------- | ------------------------------------------ |
|
||||
| **对象与字段** | 自定义数据模型(明信片、发票等) 带有类型化字段 |
|
||||
| **逻辑函数** | 由 HTTP 路由、cron 调度或数据库事件触发的服务端 TypeScript |
|
||||
| **前端组件** | 在 Twenty 的 UI 内渲染的 React 组件(侧边面板、小部件、命令菜单) |
|
||||
| **技能与智能体** | AI 能力——可复用的指令和自主助手 |
|
||||
| **视图与导航** | 预配置的列表视图和侧边栏菜单项 |
|
||||
| **页面布局** | 带有选项卡和小部件的自定义记录详情页 |
|
||||
|
||||
Full reference: [Concepts](/l/zh/developers/extend/apps/getting-started/concepts).
|
||||
完整参考:[概念](/l/zh/developers/extend/apps/getting-started/concepts)。
|
||||
|
||||
## Next steps
|
||||
## 后续步骤
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Config" icon="screwdriver-wrench" href="/l/zh/developers/extend/apps/config/overview">
|
||||
Application identity, default role, install hooks, public assets.
|
||||
<Card title="配置" icon="screwdriver-wrench" href="/l/zh/developers/extend/apps/config/overview">
|
||||
应用身份、默认角色、安装钩子、公共资源。
|
||||
</Card>
|
||||
<Card title="Data" icon="database" href="/l/zh/developers/extend/apps/data/overview">
|
||||
Objects, fields, and bidirectional relations.
|
||||
<Card title="数据" icon="database" href="/l/zh/developers/extend/apps/data/overview">
|
||||
对象、字段和双向关系。
|
||||
</Card>
|
||||
<Card title="Logic" icon="bolt" href="/l/zh/developers/extend/apps/logic/overview">
|
||||
Logic functions, skills, agents, and OAuth connections.
|
||||
<Card title="逻辑" icon="bolt" href="/l/zh/developers/extend/apps/logic/overview">
|
||||
逻辑函数、技能、代理和 OAuth 连接。
|
||||
</Card>
|
||||
<Card title="Layout" icon="table-columns" href="/l/zh/developers/extend/apps/layout/overview">
|
||||
Views, navigation, page layouts, front components.
|
||||
<Card title="布局" icon="table-columns" href="/l/zh/developers/extend/apps/layout/overview">
|
||||
视图、导航、页面布局、前端组件。
|
||||
</Card>
|
||||
<Card title="Operations" icon="rocket" href="/l/zh/developers/extend/apps/operations/overview">
|
||||
CLI, testing, remotes, CI, and publishing your app.
|
||||
<Card title="操作" icon="rocket" href="/l/zh/developers/extend/apps/operations/overview">
|
||||
CLI、测试、远程、CI,以及发布你的应用。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
---
|
||||
title: Scaffolding
|
||||
description: Generate entity files interactively with yarn twenty add — objects, fields, views, logic functions, and more.
|
||||
title: 脚手架
|
||||
description: 通过 yarn twenty add 以交互方式生成实体文件——对象、字段、视图、逻辑函数等。
|
||||
icon: wand-magic-sparkles
|
||||
---
|
||||
|
||||
Instead of creating entity files by hand, use the interactive scaffolder:
|
||||
无需手动创建实体文件,使用交互式脚手架:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add
|
||||
```
|
||||
|
||||
It prompts you to pick an entity type and walks you through the required fields, then writes a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call.
|
||||
它会提示你选择一个实体类型,引导你填写所需字段,然后生成一个可直接使用的文件,其中包含稳定的 `universalIdentifier` 和正确的 `defineEntity()` 调用。
|
||||
|
||||
You can also pass the entity type directly to skip the first prompt:
|
||||
你也可以直接传入实体类型以跳过第一个提示:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add object
|
||||
@@ -20,38 +20,38 @@ yarn twenty add logicFunction
|
||||
yarn twenty add frontComponent
|
||||
```
|
||||
|
||||
## Available entity types
|
||||
## 可用的实体类型
|
||||
|
||||
| Entity type | Command | Generated file |
|
||||
| -------------------- | ------------------------------------ | ------------------------------------------------------- |
|
||||
| Object | `yarn twenty add object` | `src/objects/\<name>.ts` |
|
||||
| Field | `yarn twenty add field` | `src/fields/\<name>.ts` |
|
||||
| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/\<name>.ts` |
|
||||
| Front component | `yarn twenty add frontComponent` | `src/front-components/\<name>.tsx` |
|
||||
| Role | `yarn twenty add role` | `src/roles/\<name>.ts` |
|
||||
| Skill | `yarn twenty add skill` | `src/skills/\<name>.ts` |
|
||||
| Agent | `yarn twenty add agent` | `src/agents/\<name>.ts` |
|
||||
| View | `yarn twenty add view` | `src/views/\<name>.ts` |
|
||||
| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
|
||||
| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/\<name>.ts` |
|
||||
| 实体类型 | 命令 | 生成的文件 |
|
||||
| ----- | ------------------------------------ | ------------------------------------------------------- |
|
||||
| 对象 | `yarn twenty add object` | `src/objects/\<name>.ts` |
|
||||
| 字段 | `yarn twenty add field` | `src/fields/\<name>.ts` |
|
||||
| 逻辑函数 | `yarn twenty add logicFunction` | `src/logic-functions/\<name>.ts` |
|
||||
| 前端组件 | `yarn twenty add frontComponent` | `src/front-components/\<name>.tsx` |
|
||||
| 角色 | `yarn twenty add role` | `src/roles/\<name>.ts` |
|
||||
| 技能 | `yarn twenty add skill` | `src/skills/\<name>.ts` |
|
||||
| 代理 | `yarn twenty add agent` | `src/agents/\<name>.ts` |
|
||||
| 视图 | `yarn twenty add view` | `src/views/\<name>.ts` |
|
||||
| 导航菜单项 | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
|
||||
| 页面布局 | `yarn twenty add pageLayout` | `src/page-layouts/\<name>.ts` |
|
||||
|
||||
## What the scaffolder generates
|
||||
## 脚手架生成的内容
|
||||
|
||||
Each entity type has its own template. For example, `yarn twenty add object` asks for:
|
||||
每种实体类型都有其自己的模板。 例如,`yarn twenty add object` 会询问:
|
||||
|
||||
1. **Name (singular)** — e.g., `invoice`
|
||||
2. **Name (plural)** — e.g., `invoices`
|
||||
3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`)
|
||||
4. **Label (plural)** — auto-populated (e.g., `Invoices`)
|
||||
5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object.
|
||||
1. **名称(单数)**——例如,`invoice`
|
||||
2. **名称(复数)**——例如,`invoices`
|
||||
3. **标签(单数)**——根据名称自动填充(例如,`Invoice`)
|
||||
4. **标签(复数)**——自动填充(例如,`Invoices`)
|
||||
5. **创建视图和导航项?**——如果你选择是,脚手架还会为新对象生成相应的视图和侧边栏链接。
|
||||
|
||||
Other entity types have simpler prompts — most only ask for a name.
|
||||
其他实体类型的提示更简单——大多只会询问名称。
|
||||
|
||||
The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`.
|
||||
`field` 实体类型更为详细:它会询问字段名称、标签、类型(从所有可用字段类型列表中选择,如 `TEXT`、`NUMBER`、`SELECT`、`RELATION` 等),以及目标对象的 `universalIdentifier`。
|
||||
|
||||
## Custom output path
|
||||
## 自定义输出路径
|
||||
|
||||
Use the `--path` flag to place the generated file in a custom location:
|
||||
使用 `--path` 标志将生成的文件放置在自定义位置:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add logicFunction --path src/custom-folder
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Command Menu Items
|
||||
description: Surface front components as quick actions and command menu (Cmd+K) entries with defineCommandMenuItem.
|
||||
title: 命令菜单项
|
||||
description: 使用 defineCommandMenuItem 将前端组件暴露为快速操作和命令菜单(Cmd+K)条目。
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
A **command menu item** is the bridge between the user and a [front component](/l/zh/developers/extend/apps/layout/front-components). It registers the component in Twenty's command menu (Cmd+K) and, optionally, as a pinned quick-action button in the top-right corner of the page.
|
||||
**命令菜单项** 是用户与[前端组件](/l/zh/developers/extend/apps/layout/front-components)之间的桥梁。 它会在 Twenty 的命令菜单(Cmd+K)中注册该组件,并可选地将其作为页面右上角的固定快速操作按钮。
|
||||
|
||||
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
@@ -20,25 +20,25 @@ export default defineCommandMenuItem({
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration fields
|
||||
## 配置字段
|
||||
|
||||
| Field | Required | Description |
|
||||
| --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `universalIdentifier` | Yes | Stable unique ID for the command |
|
||||
| `label` | Yes | Full label shown in the command menu (Cmd+K) |
|
||||
| `frontComponentUniversalIdentifier` | Yes | The `universalIdentifier` of the front component this command opens |
|
||||
| `shortLabel` | No | Shorter label displayed on the pinned quick-action button |
|
||||
| `icon` | No | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) |
|
||||
| `isPinned` | No | When `true`, shows the command as a quick-action button in the top-right corner of the page |
|
||||
| `availabilityType` | No | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) |
|
||||
| `availabilityObjectUniversalIdentifier` | No | Restrict the command to pages of a specific object type (e.g. only on Company records) |
|
||||
| `conditionalAvailabilityExpression` | No | A boolean expression that dynamically controls visibility (see below) |
|
||||
| 字段 | 必填 | 描述 |
|
||||
| --------------------------------------- | -- | -------------------------------------------------------------------------------- |
|
||||
| `universalIdentifier` | 是 | 该命令的稳定唯一 ID |
|
||||
| `label` | 是 | 在命令菜单(Cmd+K)中显示的完整标签 |
|
||||
| `frontComponentUniversalIdentifier` | 是 | 此命令打开的前端组件的 `universalIdentifier` |
|
||||
| `shortLabel` | 否 | 固定的快速操作按钮上显示的较短标签 |
|
||||
| `icon` | 否 | 显示在标签旁边的图标名称(例如 `'IconBolt'`、`'IconSend'`) |
|
||||
| `isPinned` | 否 | 为 `true` 时,会将该命令显示为页面右上角的快速操作按钮 |
|
||||
| `availabilityType` | 否 | 控制命令出现的位置:'GLOBAL'(始终可用)、'RECORD_SELECTION'(仅在选择了记录时),或 'FALLBACK'(当没有其他命令匹配时显示) |
|
||||
| `availabilityObjectUniversalIdentifier` | 否 | 将该命令限制在特定对象类型的页面上(例如仅在 Company 记录上) |
|
||||
| `conditionalAvailabilityExpression` | 否 | 用于动态控制可见性的布尔表达式(见下文) |
|
||||
|
||||
## Headless commands
|
||||
## 无头命令
|
||||
|
||||
A command menu item paired with a [headless front component](/l/zh/developers/extend/apps/layout/front-components#headless-vs-non-headless) is the idiomatic way to ship a one-click action — run code, navigate, or confirm and execute. The Front Components page covers the [SDK Command components](/l/zh/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) that handle the action-and-unmount pattern.
|
||||
与[无头前端组件](/l/zh/developers/extend/apps/layout/front-components#headless-vs-non-headless)配对的命令菜单项,是交付一键操作(运行代码、导航,或确认并执行)的惯用方式。 Front Components 页面介绍了处理“执行操作并卸载”模式的 [SDK Command 组件](/l/zh/developers/extend/apps/layout/front-components#sdk-command-components)(`Command`、`CommandLink`、`CommandModal`、`CommandOpenSidePanelPage`)。
|
||||
|
||||
A typical flow:
|
||||
一个典型的流程:
|
||||
|
||||
```tsx src/front-components/run-action.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -79,9 +79,9 @@ export default defineCommandMenuItem({
|
||||
});
|
||||
```
|
||||
|
||||
## Conditional availability expressions
|
||||
## 条件可用性表达式
|
||||
|
||||
The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions:
|
||||
通过 `conditionalAvailabilityExpression` 字段,您可以基于当前页面上下文控制命令何时可见。 从 `twenty-sdk` 导入带类型的变量和运算符来构建表达式:
|
||||
|
||||
```ts src/command-menu-items/bulk-update.command-menu-item.ts
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
@@ -103,42 +103,42 @@ export default defineCommandMenuItem({
|
||||
});
|
||||
```
|
||||
|
||||
### Context variables
|
||||
### 上下文变量
|
||||
|
||||
These represent the current state of the page:
|
||||
这些变量表示页面的当前状态:
|
||||
|
||||
| Variable | Type | Description |
|
||||
| ------------------------------ | --------- | ---------------------------------------------------------------- |
|
||||
| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) |
|
||||
| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel |
|
||||
| `numberOfSelectedRecords` | `number` | Number of currently selected records |
|
||||
| `isSelectAll` | `boolean` | Whether "select all" is active |
|
||||
| `selectedRecords` | `array` | The selected record objects |
|
||||
| `favoriteRecordIds` | `array` | IDs of favorited records |
|
||||
| `objectPermissions` | `object` | Permissions for the current object type |
|
||||
| `targetObjectReadPermissions` | `object` | Read permissions for the target object |
|
||||
| `targetObjectWritePermissions` | `object` | Write permissions for the target object |
|
||||
| `featureFlags` | `object` | Active feature flags |
|
||||
| `objectMetadataItem` | `object` | Metadata of the current object type |
|
||||
| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter |
|
||||
| 变量 | 类型 | 描述 |
|
||||
| ------------------------------ | --------- | --------------------------------------------- |
|
||||
| `pageType` | `string` | 当前页面类型(例如 'RecordIndexPage'、'RecordShowPage') |
|
||||
| `isInSidePanel` | `boolean` | 组件是否在侧边栏中渲染 |
|
||||
| `numberOfSelectedRecords` | `number` | 当前选中的记录数量 |
|
||||
| `isSelectAll` | `boolean` | “全选”是否已激活 |
|
||||
| `selectedRecords` | `array` | 已选记录对象 |
|
||||
| `favoriteRecordIds` | `array` | 已收藏记录的 ID |
|
||||
| `objectPermissions` | `object` | 当前对象类型的权限 |
|
||||
| `targetObjectReadPermissions` | `object` | 目标对象的读取权限 |
|
||||
| `targetObjectWritePermissions` | `object` | 目标对象的写入权限 |
|
||||
| `featureFlags` | `object` | 当前启用的功能标志 |
|
||||
| `objectMetadataItem` | `object` | 当前对象类型的元数据 |
|
||||
| `hasAnySoftDeleteFilterOnView` | `boolean` | 当前视图是否包含软删除筛选器 |
|
||||
|
||||
### Operators
|
||||
### 运算符
|
||||
|
||||
Combine variables into boolean expressions:
|
||||
将变量组合为布尔表达式:
|
||||
|
||||
| Operator | Description |
|
||||
| ----------------------------------- | ----------------------------------------------------------------- |
|
||||
| `isDefined(value)` | `true` if the value is not null/undefined |
|
||||
| `isNonEmptyString(value)` | `true` if the value is a non-empty string |
|
||||
| `includes(array, value)` | `true` if the array contains the value |
|
||||
| `includesEvery(array, prop, value)` | `true` if every item's property includes the value |
|
||||
| `every(array, prop)` | `true` if the property is truthy on every item |
|
||||
| `everyDefined(array, prop)` | `true` if the property is defined on every item |
|
||||
| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item |
|
||||
| `some(array, prop)` | `true` if the property is truthy on at least one item |
|
||||
| `someDefined(array, prop)` | `true` if the property is defined on at least one item |
|
||||
| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item |
|
||||
| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item |
|
||||
| `none(array, prop)` | `true` if the property is falsy on every item |
|
||||
| `noneDefined(array, prop)` | `true` if the property is undefined on every item |
|
||||
| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item |
|
||||
| 运算符 | 描述 |
|
||||
| ----------------------------------- | -------------------------------- |
|
||||
| `isDefined(value)` | 当该值不是 null/undefined 时为 `true` |
|
||||
| `isNonEmptyString(value)` | 当该值为非空字符串时为 `true` |
|
||||
| `includes(array, value)` | 当数组包含该值时为 `true` |
|
||||
| `includesEvery(array, prop, value)` | 当每个条目的属性都包含该值时为 `true` |
|
||||
| `every(array, prop)` | 当该属性在每个条目上都为 truthy 时为 `true` |
|
||||
| `everyDefined(array, prop)` | 当该属性在每个条目上都已定义时为 `true` |
|
||||
| `everyEquals(array, prop, value)` | 当该属性在每个条目上都等于该值时为 `true` |
|
||||
| `some(array, prop)` | 当至少一个条目上的该属性为 truthy 时为 `true` |
|
||||
| `someDefined(array, prop)` | 当至少一个条目上的该属性已定义时为 `true` |
|
||||
| `someEquals(array, prop, value)` | 当至少一个条目上的该属性等于该值时为 `true` |
|
||||
| `someNonEmptyString(array, prop)` | 当至少一个条目上的该属性为非空字符串时为 `true` |
|
||||
| `none(array, prop)` | 当该属性在每个条目上都为 falsy 时为 `true` |
|
||||
| `noneDefined(array, prop)` | 当该属性在每个条目上都为 undefined 时为 `true` |
|
||||
| `noneEquals(array, prop, value)` | 当该属性在任意条目上都不等于该值时为 `true` |
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
---
|
||||
title: Front Components
|
||||
description: Build React components that render inside Twenty's UI with sandboxed isolation.
|
||||
title: 前端组件
|
||||
description: 构建可在 Twenty 的 UI 中渲染并具备沙盒隔离的 React 组件。
|
||||
icon: window-maximize
|
||||
---
|
||||
|
||||
Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code is sandboxed but renders natively in the page, not in an iframe.
|
||||
前端组件是直接在 Twenty 的 UI 内渲染的 React 组件。 它们在使用 Remote DOM 的**隔离 Web Worker**中运行——你的代码在沙盒中执行,但会原生渲染到页面中,而非在 iframe 里。
|
||||
|
||||
## Where front components can be used
|
||||
## 前端组件可用位置
|
||||
|
||||
Front components can render in two locations within Twenty:
|
||||
在 Twenty 中,前端组件可在两个位置进行渲染:
|
||||
|
||||
* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu.
|
||||
* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside [page layouts](/l/zh/developers/extend/apps/layout/page-layouts). When configuring a dashboard or a record page layout, users can add a front component widget.
|
||||
* **侧边栏** — 非无头的前端组件会在右侧侧边栏中打开。 当前端组件从命令菜单触发时,这是默认行为。
|
||||
* **小部件(仪表盘和记录页面)** — 前端组件可以作为小部件嵌入到[页面布局](/l/zh/developers/extend/apps/layout/page-layouts)中。 在配置仪表盘或记录页面布局时,用户可以添加前端组件小部件。
|
||||
|
||||
A front component on its own isn't reachable from the UI — you need to *surface* it. The two ways to do that are:
|
||||
单独存在的前端组件无法从界面中访问 —— 你需要将它*呈现*出来。 实现这一点有两种方式:
|
||||
|
||||
* **Pair it with a [command menu item](/l/zh/developers/extend/apps/layout/command-menu-items)** — registers it in the command menu (Cmd+K) and, optionally, as a pinned quick-action.
|
||||
* **Embed it as a widget in a [page layout](/l/zh/developers/extend/apps/layout/page-layouts)** — places it on a record's detail page or dashboard.
|
||||
* **将它与[命令菜单项](/l/zh/developers/extend/apps/layout/command-menu-items)配对** —— 将其注册到命令菜单(Cmd+K)中,并可选地将其设为固定快速操作。
|
||||
* **将它作为小部件嵌入到[页面布局](/l/zh/developers/extend/apps/layout/page-layouts)中** —— 将其放置在记录详情页面或仪表盘上。
|
||||
|
||||
## Basic example
|
||||
## 基础示例
|
||||
|
||||
The quickest way to see a front component in action is to pair it with a [`defineCommandMenuItem`](/l/zh/developers/extend/apps/layout/command-menu-items), so it appears as a quick-action button in the top-right corner of the page:
|
||||
最快看到前端组件实际效果的方式是将它与[`defineCommandMenuItem`](/l/zh/developers/extend/apps/layout/command-menu-items)配对,这样它就会显示为页面右上角的快速操作按钮:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -56,35 +56,35 @@ export default defineCommandMenuItem({
|
||||
});
|
||||
```
|
||||
|
||||
After syncing with `yarn twenty dev` (or running a one-shot `yarn twenty dev --once`), the quick action appears in the top-right corner of the page:
|
||||
使用 `yarn twenty dev` 同步后(或单次运行 `yarn twenty dev --once`),快速操作会出现在页面右上角:
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Quick action button in the top-right corner" />
|
||||
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="右上角的快速操作按钮" />
|
||||
</div>
|
||||
|
||||
Click it to render the component inline.
|
||||
点击它以内联方式渲染该组件。
|
||||
|
||||
## Configuration fields
|
||||
## 配置字段
|
||||
|
||||
| Field | Required | Description |
|
||||
| --------------------- | -------- | ------------------------------------------------------------ |
|
||||
| `universalIdentifier` | Yes | Stable unique ID for this component |
|
||||
| `component` | Yes | A React component function |
|
||||
| `name` | No | Display name |
|
||||
| `description` | No | Description of what the component does |
|
||||
| `isHeadless` | No | Set to `true` if the component has no visible UI (see below) |
|
||||
| 字段 | 必填 | 描述 |
|
||||
| --------------------- | -- | ---------------------------- |
|
||||
| `universalIdentifier` | 是 | 该组件的稳定唯一 ID |
|
||||
| `component` | 是 | 一个 React 组件函数 |
|
||||
| `name` | 否 | 显示名称 |
|
||||
| `description` | 否 | 组件的功能描述 |
|
||||
| `isHeadless` | 否 | 如果组件没有可见的 UI,则设为 `true`(见下文) |
|
||||
|
||||
## Placing a front component on a page
|
||||
## 在页面上放置前端组件
|
||||
|
||||
Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See [Page Layouts](/l/zh/developers/extend/apps/layout/page-layouts) for details.
|
||||
除了命令之外,你还可以在**页面布局**中将其添加为小部件,从而将前端组件直接嵌入记录页面。 详情请参见[页面布局](/l/zh/developers/extend/apps/layout/page-layouts)。
|
||||
|
||||
## Headless vs non-headless
|
||||
## 无头与非无头
|
||||
|
||||
Front components come in two rendering modes controlled by the `isHeadless` option:
|
||||
前端组件有两种由 `isHeadless` 选项控制的渲染模式:
|
||||
|
||||
**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted.
|
||||
**非无头(默认)** — 该组件会渲染可见的 UI。 从命令菜单触发时,它会在侧边栏中打开。 当 `isHeadless` 为 `false` 或被省略时,这是默认行为。
|
||||
|
||||
**Headless (`isHeadless: true`)** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below.
|
||||
**无头 (`isHeadless: true`)** — 该组件会在后台以不可见的方式挂载。 它不会打开侧边栏。 无头组件旨在用于执行逻辑后自行卸载的操作——例如运行异步任务、导航到某个页面或显示确认模态框。 它们与下文介绍的 SDK Command 组件天然契合。
|
||||
|
||||
```tsx src/front-components/sync-tracker.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -110,20 +110,20 @@ export default defineFrontComponent({
|
||||
});
|
||||
```
|
||||
|
||||
Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API.
|
||||
由于该组件返回 `null`,Twenty 会跳过为其渲染容器——布局中不会出现空白区域。 该组件仍可访问所有 hooks 和宿主通信 API。
|
||||
|
||||
## SDK Command components
|
||||
## SDK Command 组件
|
||||
|
||||
The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done.
|
||||
`twenty-sdk` 包提供了四个为无头前端组件设计的 Command 辅助组件。 每个组件都会在挂载时执行一个操作,通过显示 snackbar 通知来处理错误,并在完成后自动卸载该前端组件。
|
||||
|
||||
Import them from `twenty-sdk/command`:
|
||||
从 `twenty-sdk/command` 导入它们:
|
||||
|
||||
* **`Command`** — Runs an async callback via the `execute` prop.
|
||||
* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`.
|
||||
* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
|
||||
* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`.
|
||||
* **`Command`** — 通过 `execute` 属性运行异步回调。
|
||||
* **`CommandLink`** — 导航到某个应用路径。 属性:`to`、`params`、`queryParams`、`options`。
|
||||
* **`CommandModal`** — 打开一个确认模态框。 如果用户确认,则执行 `execute` 回调。 属性:`title`、`subtitle`、`execute`、`confirmButtonText`、`confirmButtonAccent`。
|
||||
* **`CommandOpenSidePanelPage`** — 打开特定的侧边栏页面。 属性:`page`、`pageTitle`、`pageIcon`。
|
||||
|
||||
Here is a full example of a headless front component using `Command` to run an action from the command menu:
|
||||
下面是一个完整示例:无头前端组件使用 `Command` 从命令菜单运行一个操作:
|
||||
|
||||
```tsx src/front-components/run-action.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -165,7 +165,7 @@ export default defineCommandMenuItem({
|
||||
});
|
||||
```
|
||||
|
||||
And an example using `CommandModal` to ask for confirmation before executing:
|
||||
另一个示例:使用 `CommandModal` 在执行前请求确认:
|
||||
|
||||
```tsx src/front-components/delete-draft.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -196,9 +196,9 @@ export default defineFrontComponent({
|
||||
});
|
||||
```
|
||||
|
||||
## Accessing runtime context
|
||||
## 访问运行时上下文
|
||||
|
||||
Inside your component, use SDK hooks to access the current user, record, and component instance:
|
||||
在组件内部,使用 SDK 的 hooks 获取当前用户、记录和组件实例:
|
||||
|
||||
```tsx src/front-components/record-info.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -229,31 +229,31 @@ export default defineFrontComponent({
|
||||
});
|
||||
```
|
||||
|
||||
Available hooks:
|
||||
可用的 hooks:
|
||||
|
||||
| Hook | Returns | Description |
|
||||
| --------------------------------------------- | ------------------ | ---------------------------------------------------------- |
|
||||
| `useUserId()` | `string` or `null` | The current user's ID |
|
||||
| `useSelectedRecordIds()` | `string[]` | All selected record IDs (empty array if none selected) |
|
||||
| `useRecordId()` | `string` or `null` | **Deprecated.** Use `useSelectedRecordIds()` instead |
|
||||
| `useFrontComponentId()` | `string` | This component instance's ID |
|
||||
| `useFrontComponentExecutionContext(selector)` | varies | Access the full execution context with a selector function |
|
||||
| 钩子 | 返回值 | 描述 |
|
||||
| --------------------------------------------- | ----------------- | ------------------------------------- |
|
||||
| `useUserId()` | `string` 或 `null` | 当前用户的 ID |
|
||||
| `useSelectedRecordIds()` | `字符串[]` | 所有已选择的记录 ID(如果未选择,则为空数组) |
|
||||
| `useRecordId()` | `string` 或 `null` | **已弃用。** 请改用 `useSelectedRecordIds()` |
|
||||
| `useFrontComponentId()` | `string` | 此组件实例的 ID |
|
||||
| `useFrontComponentExecutionContext(selector)` | 因情况而异 | 使用选择器函数访问完整的执行上下文 |
|
||||
|
||||
## Host communication API
|
||||
## 宿主通信 API
|
||||
|
||||
Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`:
|
||||
前端组件可以使用来自 `twenty-sdk` 的函数触发导航、模态框和通知:
|
||||
|
||||
| Function | Description |
|
||||
| ----------------------------------------------- | ----------------------------- |
|
||||
| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app |
|
||||
| `openSidePanelPage(params)` | Open a side panel |
|
||||
| `closeSidePanel()` | Close the side panel |
|
||||
| `openCommandConfirmationModal(params)` | Show a confirmation dialog |
|
||||
| `enqueueSnackbar(params)` | Show a toast notification |
|
||||
| `unmountFrontComponent()` | Unmount the component |
|
||||
| `updateProgress(progress)` | Update a progress indicator |
|
||||
| 函数 | 描述 |
|
||||
| ----------------------------------------------- | ------------- |
|
||||
| `navigate(to, params?, queryParams?, options?)` | 在应用中导航到某个页面 |
|
||||
| `openSidePanelPage(params)` | 打开侧边栏 |
|
||||
| `closeSidePanel()` | 关闭侧边栏 |
|
||||
| `openCommandConfirmationModal(params)` | 显示确认对话框 |
|
||||
| `enqueueSnackbar(params)` | 显示一条 Toast 通知 |
|
||||
| `unmountFrontComponent()` | 卸载该组件 |
|
||||
| `updateProgress(progress)` | 更新进度指示器 |
|
||||
|
||||
Here is an example that uses the host API to show a snackbar and close the side panel after an action completes:
|
||||
下面是一个示例,使用宿主 API 在操作完成后显示一条 snackbar 并关闭侧边栏:
|
||||
|
||||
```tsx src/front-components/archive-record.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -298,9 +298,9 @@ export default defineFrontComponent({
|
||||
});
|
||||
```
|
||||
|
||||
### Working with multiple records
|
||||
### 处理多个记录
|
||||
|
||||
Use `useSelectedRecordIds()` to handle multiple selected records. This is useful for bulk operations:
|
||||
使用 `useSelectedRecordIds()` 来处理多个已选记录。 这对于批量操作很有用:
|
||||
|
||||
```tsx src/front-components/bulk-export.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -353,9 +353,9 @@ export default defineFrontComponent({
|
||||
});
|
||||
```
|
||||
|
||||
## Public assets
|
||||
## 公共资源
|
||||
|
||||
Front components can access files from the app's `public/` directory using `getPublicAssetUrl`:
|
||||
前端组件可以使用 `getPublicAssetUrl` 访问应用的 `public/` 目录中的文件:
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
@@ -369,18 +369,18 @@ export default defineFrontComponent({
|
||||
});
|
||||
```
|
||||
|
||||
See the [public assets section](/l/zh/developers/extend/apps/config/public-assets) for details.
|
||||
详情请参见[公共资源部分](/l/zh/developers/extend/apps/config/public-assets)。
|
||||
|
||||
## Styling
|
||||
## 样式
|
||||
|
||||
Front components support multiple styling approaches. You can use:
|
||||
前端组件支持多种样式方案。 你可以使用:
|
||||
|
||||
* **Inline styles** — `style={{ color: 'red' }}`
|
||||
* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more)
|
||||
* **Emotion** — CSS-in-JS with `@emotion/react`
|
||||
* **Styled-components** — `styled.div` patterns
|
||||
* **Tailwind CSS** — utility classes
|
||||
* **Any CSS-in-JS library** compatible with React
|
||||
* **内联样式** — `style={{ color: 'red' }}`
|
||||
* **Twenty UI 组件** — 从 `twenty-sdk/ui` 导入(Button、Tag、Status、Chip、Avatar 等)
|
||||
* **Emotion** — 使用 `@emotion/react` 的 CSS-in-JS
|
||||
* **Styled-components** — `styled.div` 模式
|
||||
* **Tailwind CSS** — 工具类
|
||||
* **任何 CSS-in-JS 库**(与 React 兼容)
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Navigation Menu Items
|
||||
description: Add custom entries to the workspace sidebar — links to saved views or external URLs.
|
||||
title: 导航菜单项
|
||||
description: 向工作区侧边栏添加自定义条目——指向已保存视图或外部 URL 的链接。
|
||||
icon: bars
|
||||
---
|
||||
|
||||
A **navigation menu item** is an entry in the left sidebar. Use `defineNavigationMenuItem()` to ship custom sidebar links — typically one per [view](/l/zh/developers/extend/apps/layout/views) you ship — or to point at external URLs.
|
||||
**导航菜单项** 是左侧边栏中的一个条目。 使用 `defineNavigationMenuItem()` 发布自定义侧边栏链接——通常为你发布的每个[视图](/l/zh/developers/extend/apps/layout/views)提供一个链接——或指向外部 URL。
|
||||
|
||||
```ts src/navigation-menu-items/example-navigation-menu-item.ts
|
||||
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
|
||||
@@ -21,24 +21,24 @@ export default defineNavigationMenuItem({
|
||||
});
|
||||
```
|
||||
|
||||
## Key points
|
||||
## 关键点
|
||||
|
||||
* `type` determines what the menu item links to. Each type pairs with a specific identifier field:
|
||||
* `type` 决定菜单项链接到什么。 每种类型都与一个特定的标识符字段配对:
|
||||
|
||||
| 类型 | 作用 | Required field |
|
||||
| ------------------------------------ | ------------------------------------ | ----------------------------------------------------------------------------- |
|
||||
| `NavigationMenuItemType.VIEW` | Opens a saved view | `viewUniversalIdentifier` |
|
||||
| `NavigationMenuItemType.LINK` | Opens an external URL | `link` |
|
||||
| `NavigationMenuItemType.FOLDER` | Groups nested items under a label | `name` (and child items reference the folder via `folderUniversalIdentifier`) |
|
||||
| `NavigationMenuItemType.OBJECT` | Opens an object's default index page | `targetObjectUniversalIdentifier` |
|
||||
| `NavigationMenuItemType.PAGE_LAYOUT` | Opens a standalone page layout | `pageLayoutUniversalIdentifier` |
|
||||
| 类型 | 作用 | 必填字段 |
|
||||
| ------------------------------------ | ----------- | ----------------------------------------------- |
|
||||
| `NavigationMenuItemType.VIEW` | 打开已保存视图 | `viewUniversalIdentifier` |
|
||||
| `NavigationMenuItemType.LINK` | 打开外部 URL | `link` |
|
||||
| `NavigationMenuItemType.FOLDER` | 在一个标签下分组嵌套项 | `name`(子项通过 `folderUniversalIdentifier` 引用该文件夹) |
|
||||
| `NavigationMenuItemType.OBJECT` | 打开对象的默认索引页面 | `targetObjectUniversalIdentifier` |
|
||||
| `NavigationMenuItemType.PAGE_LAYOUT` | 打开独立页面布局 | `pageLayoutUniversalIdentifier` |
|
||||
|
||||
* `position` controls ordering in the sidebar.
|
||||
* `position` 控制在侧边栏中的排序。
|
||||
|
||||
* `icon` and `color` are optional and customize how the entry looks.
|
||||
* `icon` 和 `color` 是可选的,用于自定义条目的外观。
|
||||
|
||||
* `folderUniversalIdentifier` is also available on any item to nest it inside a `FOLDER`-type parent.
|
||||
* `folderUniversalIdentifier` 也可用于任意条目,将其嵌套到一个 `FOLDER` 类型的父级中。
|
||||
|
||||
<Note>
|
||||
**Common pitfall:** creating an object without an associated view + navigation menu item makes that object invisible to users. Unless it's a technical/internal object, every custom object should have a default view *and* a sidebar entry pointing at it.
|
||||
**常见陷阱:** 创建一个没有关联视图和导航菜单项的对象会使该对象对用户不可见。 除非是技术性/内部对象,否则每个自定义对象都应具有默认视图,*并且* 侧边栏中要有一个指向它的条目。
|
||||
</Note>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Place your app inside Twenty's UI — sidebar entries, saved views, record page tabs, and sandboxed React components.
|
||||
title: 概览
|
||||
description: 将你的应用嵌入 Twenty 的 UI 中——侧边栏条目、已保存视图、记录页面选项卡以及沙箱化的 React 组件。
|
||||
icon: table-columns
|
||||
---
|
||||
|
||||
A Twenty app's **layout layer** is everything the user sees: where the app surfaces in the sidebar, which list views it ships, how its record detail pages are arranged, and which custom React components render inside those pages.
|
||||
Twenty 应用的**布局层**是用户看到的一切:应用在侧边栏中的呈现位置、它随附的列表视图、记录详情页的排布方式,以及在这些页面中渲染的自定义 React 组件。
|
||||
|
||||
```text
|
||||
Sidebar Record list Record detail page
|
||||
@@ -23,34 +23,34 @@ A Twenty app's **layout layer** is everything the user sees: where the app surfa
|
||||
and filters └─────────────────────┘
|
||||
```
|
||||
|
||||
## In this section
|
||||
## 本节内容
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Views" icon="list" href="/l/zh/developers/extend/apps/layout/views">
|
||||
`defineView` — saved list configurations: visible columns, filters, groups.
|
||||
<Card title="视图" icon="列表" href="/l/zh/developers/extend/apps/layout/views">
|
||||
`defineView` — 已保存的列表配置:可见列、筛选器、分组。
|
||||
</Card>
|
||||
<Card title="Navigation Menu Items" icon="bars" href="/l/zh/developers/extend/apps/layout/navigation-menu-items">
|
||||
`defineNavigationMenuItem` — sidebar entries pointing at views or external URLs.
|
||||
<Card title="导航菜单项" icon="bars" href="/l/zh/developers/extend/apps/layout/navigation-menu-items">
|
||||
`defineNavigationMenuItem` — 指向视图或外部 URL 的侧边栏条目。
|
||||
</Card>
|
||||
<Card title="Page Layouts" icon="table-columns" href="/l/zh/developers/extend/apps/layout/page-layouts">
|
||||
`definePageLayout` and `definePageLayoutTab` — tabs and widgets on a record's detail page.
|
||||
<Card title="页面布局" icon="table-columns" href="/l/zh/developers/extend/apps/layout/page-layouts">
|
||||
`definePageLayout` 和 `definePageLayoutTab` — 记录详情页上的选项卡和小部件。
|
||||
</Card>
|
||||
<Card title="Front Components" icon="window-maximize" href="/l/zh/developers/extend/apps/layout/front-components">
|
||||
`defineFrontComponent` — sandboxed React components that render inside Twenty.
|
||||
<Card title="前端组件" icon="window-maximize" href="/l/zh/developers/extend/apps/layout/front-components">
|
||||
`defineFrontComponent` — 在 Twenty 内部渲染的沙箱化 React 组件。
|
||||
</Card>
|
||||
<Card title="Command Menu Items" icon="terminal" href="/l/zh/developers/extend/apps/layout/command-menu-items">
|
||||
`defineCommandMenuItem` — register front components as Cmd+K entries and quick actions.
|
||||
<Card title="命令菜单项" icon="terminal" href="/l/zh/developers/extend/apps/layout/command-menu-items">
|
||||
`defineCommandMenuItem` — 将前端组件注册为 Cmd+K 条目和快速操作。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Where the app surfaces
|
||||
## 应用的呈现位置
|
||||
|
||||
| Surface | What it controls | Entity |
|
||||
| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------- |
|
||||
| **Sidebar** | A custom entry linking to a saved view or external URL | `defineNavigationMenuItem` |
|
||||
| **Record list** | A saved configuration for an object — visible columns, order, filters, groups | `defineView` |
|
||||
| **Record detail page** | The tabs and widgets on a record page (your own object's, or a standard one) | `definePageLayout`, `definePageLayoutTab` |
|
||||
| **Inside any of the above** | A custom React widget — buttons, forms, dashboards, integrations | `defineFrontComponent` |
|
||||
| **Command menu (Cmd+K)** | A pinned quick action or hidden command | `defineCommandMenuItem` |
|
||||
| 呈现位置 | 控制内容 | 实体 |
|
||||
| --------------- | --------------------------- | ----------------------------------------- |
|
||||
| **侧边栏** | 链接到已保存视图或外部 URL 的自定义条目 | `defineNavigationMenuItem` |
|
||||
| **记录列表** | 对象的已保存配置——可见列、顺序、筛选器、分组 | `defineView` |
|
||||
| **记录详情页** | 记录页面上的选项卡和小部件(你自己的对象或标准对象) | `definePageLayout`, `definePageLayoutTab` |
|
||||
| **以上任意位置内部** | 自定义 React 小部件——按钮、表单、仪表盘、集成 | `defineFrontComponent` |
|
||||
| **命令菜单(Cmd+K)** | 固定的快速操作或隐藏命令 | `defineCommandMenuItem` |
|
||||
|
||||
Front components run inside an isolated Web Worker using Remote DOM — they render *natively* in the page (not inside an iframe), but cannot reach the host page or DOM directly. Communication with Twenty happens through a message-passing host API.
|
||||
前端组件在使用 Remote DOM 的隔离 Web Worker 内运行——它们在页面中*原生*渲染(而不是在 iframe 中),但无法直接访问宿主页面或 DOM。 与 Twenty 的通信通过消息传递的宿主 API 完成。
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
title: Page Layouts
|
||||
description: Customize record detail pages — tabs, widgets, and where front components render — using definePageLayout and definePageLayoutTab.
|
||||
title: 页面布局
|
||||
description: 使用 `definePageLayout` 和 `definePageLayoutTab` 自定义记录详情页——包括选项卡、小部件以及前端组件的渲染位置。
|
||||
icon: table-columns
|
||||
---
|
||||
|
||||
A **page layout** controls how a record's detail page is arranged: which tabs appear and what widgets they contain. Use `definePageLayout()` to declare a layout for an object you own, or `definePageLayoutTab()` to add a single tab to a layout that already exists (yours or a standard Twenty one).
|
||||
**页面布局(page layout)** 控制记录详情页的排布方式:显示哪些选项卡,以及这些选项卡中包含哪些小部件。 使用 `definePageLayout()` 为你拥有的对象声明一个布局,或者使用 `definePageLayoutTab()` 为已存在的布局(你的自定义布局或标准的 Twenty 布局)添加单个选项卡。
|
||||
|
||||
| Use case | Entity |
|
||||
| ---------------------------------------------------------------------- | --------------------- |
|
||||
| Define the entire layout for a record page on an object you own | `definePageLayout` |
|
||||
| Add one tab to an existing layout (your own object, or a standard one) | `definePageLayoutTab` |
|
||||
| 用例 | 实体 |
|
||||
| ---------------------------- | --------------------- |
|
||||
| 为你拥有的对象上的记录页面定义完整布局 | `definePageLayout` |
|
||||
| 向现有布局中添加一个选项卡(你自己的对象或一个标准对象) | `definePageLayoutTab` |
|
||||
|
||||
## definePageLayout
|
||||
|
||||
Use this when you own the entire detail page — typically for a custom object you defined yourself.
|
||||
当你拥有整个详情页时使用此方式——通常适用于你自己定义的自定义对象。
|
||||
|
||||
```ts src/page-layouts/example-record-page-layout.ts
|
||||
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
|
||||
@@ -49,17 +49,17 @@ export default definePageLayout({
|
||||
});
|
||||
```
|
||||
|
||||
### Key points
|
||||
### 关键点
|
||||
|
||||
* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object.
|
||||
* `objectUniversalIdentifier` specifies which object this layout applies to.
|
||||
* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout).
|
||||
* Each `widget` inside a tab can render a [front component](/l/zh/developers/extend/apps/layout/front-components), a relation list, or other built-in widget types.
|
||||
* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones.
|
||||
* `type` 通常为 'RECORD_PAGE',用于自定义特定对象的详情视图。
|
||||
* `objectUniversalIdentifier` 指定此布局适用于哪个对象。
|
||||
* 每个 `tab` 使用 `title`、`position` 和 `layoutMode` 定义页面的一个部分(`CANVAS` 表示自由布局)。
|
||||
* 选项卡内的每个 `widget` 可以渲染一个[前端组件](/l/zh/developers/extend/apps/layout/front-components)、关系列表或其他内置小部件类型。
|
||||
* 选项卡上的 `position` 控制其顺序。 使用更高的值(例如 50)可将自定义选项卡放在内置选项卡之后。
|
||||
|
||||
## definePageLayoutTab
|
||||
|
||||
Use this when you only want to **add** a tab to an existing layout — for example, an analytics tab on the standard Company page, or an AI summary tab attached to your own object's layout.
|
||||
当你只想在现有布局中**添加**一个选项卡时使用此方式——例如,在标准 Company 页面上添加一个分析选项卡,或者在你自己的对象布局上附加一个 AI 摘要选项卡。
|
||||
|
||||
```ts src/page-layouts/example-extra-tab.ts
|
||||
import {
|
||||
@@ -94,9 +94,9 @@ export default definePageLayoutTab({
|
||||
});
|
||||
```
|
||||
|
||||
### Key points
|
||||
### 关键点
|
||||
|
||||
* `pageLayoutUniversalIdentifier` is **required** and must point to a page layout that already exists at install time — either a standard Twenty layout or one defined by your own app. Cross-app references to layouts owned by another installed app are not supported today. When the parent layout is missing, installation fails with a clear validation error.
|
||||
* `widgets` are scoped to this tab only — they reference [front components](/l/zh/developers/extend/apps/layout/front-components), views, etc. exactly like widgets defined inline in `definePageLayout`.
|
||||
* `position` controls ordering against existing tabs on the targeted layout. Pick a value that places your tab where you want it relative to built-in tabs.
|
||||
* Use this instead of `definePageLayout` when you only want to add to an existing layout. Use `definePageLayout` when you own the entire layout.
|
||||
* `pageLayoutUniversalIdentifier` 是**必需的**,并且必须在安装时指向一个已存在的页面布局——可以是标准的 Twenty 布局,也可以是由你自己的应用定义的布局。 当前不支持跨应用引用由其他已安装应用拥有的布局。 当父布局缺失时,安装会失败,并给出清晰的验证错误。
|
||||
* `widgets` 仅作用于此选项卡——它们引用[前端组件](/l/zh/developers/extend/apps/layout/front-components)、视图等,其方式与在 `definePageLayout` 中内联定义的小部件完全相同。
|
||||
* `position` 控制目标布局中相对于现有选项卡的排序。 选择一个取值,使你的选项卡相对于内置选项卡位于你想要的位置。
|
||||
* 当你只想向现有布局进行添加时,请使用此功能,而不是 `definePageLayout`。 当你拥有整个布局时,请使用 `definePageLayout`。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Views
|
||||
description: Ship pre-configured saved views — column order, filters, groups — for objects in your app.
|
||||
title: 视图
|
||||
description: 为你应用中的对象提供预配置的已保存视图——列顺序、筛选器、分组等。
|
||||
icon: list
|
||||
---
|
||||
|
||||
A **view** is a saved configuration for how records of an object are displayed: which fields appear, their order, whether they're visible, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app — typically a default index view for each custom object you create.
|
||||
**视图(view)** 是关于对象记录如何显示的已保存配置:包括显示哪些字段、它们的顺序、是否可见,以及应用的任何筛选器或分组。 使用 `defineView()` 为你的应用提供预配置视图——通常是你创建的每个自定义对象的默认索引视图。
|
||||
|
||||
```ts src/views/example-view.ts
|
||||
import { defineView, ViewKey } from 'twenty-sdk/define';
|
||||
@@ -30,14 +30,14 @@ export default defineView({
|
||||
});
|
||||
```
|
||||
|
||||
## Key points
|
||||
## 关键点
|
||||
|
||||
* `objectUniversalIdentifier` specifies which object this view applies to. It can be a custom object you defined or a standard Twenty object.
|
||||
* `key` determines the view type — `ViewKey.INDEX` is the main list view for the object.
|
||||
* `fields` controls which columns appear and in what order. Each field references a `fieldMetadataUniversalIdentifier`.
|
||||
* You can also declare `filters`, `filterGroups`, `groups`, and `fieldGroups` for advanced configurations.
|
||||
* `position` controls ordering when multiple views exist for the same object.
|
||||
* `objectUniversalIdentifier` 指定此视图适用于哪个对象。 它可以是你定义的自定义对象,也可以是标准的 Twenty 对象。
|
||||
* `key` 决定视图类型——`ViewKey.INDEX` 是该对象的主列表视图。
|
||||
* `fields` 控制显示哪些列以及它们的顺序。 每个字段引用一个 `fieldMetadataUniversalIdentifier`。
|
||||
* 你还可以声明 `filters`、`filterGroups`、`groups` 和 `fieldGroups` 以进行更高级的配置。
|
||||
* 当同一对象存在多个视图时,`position` 控制其排序。
|
||||
|
||||
## How views show up in the UI
|
||||
## 视图在 UI 中如何显示
|
||||
|
||||
A view by itself isn't reachable from the sidebar. To make it appear there, pair it with a [navigation menu item](/l/zh/developers/extend/apps/layout/navigation-menu-items) of type `VIEW` that points at the view's `universalIdentifier`. That's the canonical pattern: every custom object typically ships a default view + a sidebar entry that opens it.
|
||||
单独一个视图无法从侧边栏访问。 要让它显示在侧边栏中,请将其与类型为 `VIEW`、指向该视图 `universalIdentifier` 的[导航菜单项](/l/zh/developers/extend/apps/layout/navigation-menu-items)配对。 这是规范用法:每个自定义对象通常都会提供一个默认视图,以及一个在侧边栏中打开该视图的条目。
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
---
|
||||
title: Connections
|
||||
description: Let your app act on a user's behalf in third-party services via OAuth.
|
||||
title: 连接
|
||||
description: 通过 OAuth 让你的应用代表用户在第三方服务中执行操作。
|
||||
icon: plug
|
||||
---
|
||||
|
||||
Connections are credentials a user holds for an external service (Linear, GitHub, Slack, ...). Your app declares **how** those credentials are obtained — a **connection provider** — and consumes them at runtime to make authenticated calls to the third-party API.
|
||||
连接是用户为外部服务(Linear、GitHub、Slack 等)持有的凭据。 你的应用声明**如何**获取这些凭据——即**连接提供程序**——并在运行时使用它们向第三方 API 发起认证调用。
|
||||
|
||||
Today only OAuth 2.0 is supported. Future credential types (personal access tokens, API keys, basic auth) will plug into the same surface — apps already using `defineConnectionProvider({ type: 'oauth', ... })` won't need to migrate.
|
||||
目前仅支持 OAuth 2.0。 将来的凭据类型(个人访问令牌、API 密钥、基本身份验证)将接入相同的接口——已经使用 `defineConnectionProvider({ type: 'oauth', ... })` 的应用将无需迁移。
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
<Accordion title="defineConnectionProvider" description="Declare how your app's connections are obtained">
|
||||
<Accordion title="defineConnectionProvider" description="声明你的应用如何获取连接">
|
||||
|
||||
A connection provider describes the OAuth handshake your app needs. The user clicks "Add connection" in your app's settings, completes the provider's consent screen, and a `ConnectedAccount` row is created in their workspace.
|
||||
连接提供程序描述了你的应用所需的 OAuth 握手流程。 用户在你的应用设置中点击"添加连接",完成提供方的授权同意页面后,会在其工作区中创建一条 `ConnectedAccount` 行。
|
||||
|
||||
A working setup needs **two files** — the connection provider, and a matching `serverVariables` declaration on `defineApplication` that holds the OAuth client credentials.
|
||||
一个可用的配置需要**两个文件**——连接提供程序,以及在 `defineApplication` 上与之匹配、用于保存 OAuth 客户端凭据的 `serverVariables` 声明。
|
||||
|
||||
```ts src/connection-providers/linear-connection.ts
|
||||
import { defineConnectionProvider } from 'twenty-sdk/define';
|
||||
@@ -71,16 +71,16 @@ export default defineApplication({
|
||||
});
|
||||
```
|
||||
|
||||
Key points:
|
||||
关键点:
|
||||
|
||||
* `name` is the unique identifier string used in `listConnections({ providerName })` (kebab-case, must match `^[a-z][a-z0-9-]*$`).
|
||||
* `displayName` shows in the per-app settings tab and in the AI tool list.
|
||||
* `clientIdVariable` / `clientSecretVariable` are **names**, not values — they must match keys declared in `defineApplication.serverVariables`. The actual `client_id` and `client_secret` are entered by the server admin through the app registration UI, never committed to your repo.
|
||||
* Use `serverVariables` (not `applicationVariables`) — OAuth credentials are server-wide and one OAuth app per Twenty server.
|
||||
* Until both `serverVariables` are filled in, the per-app settings tab shows a "needs server admin" hint and the "Add connection" button is disabled.
|
||||
* `type: 'oauth'` is the only supported value today. The discriminator is forward-compatible: future types (`'pat'`, `'api-key'`, ...) will add new sub-config blocks alongside `oauth`.
|
||||
* `name` 是在 `listConnections({ providerName })` 中使用的唯一标识符字符串(短横线命名(kebab-case),必须匹配 `^[a-z][a-z0-9-]*$`)。
|
||||
* `displayName` 会显示在每个应用的设置选项卡以及 AI 工具列表中。
|
||||
* `clientIdVariable` / `clientSecretVariable` 是**名称**,而不是值——它们必须与 `defineApplication.serverVariables` 中声明的键匹配。 实际的 `client_id` 和 `client_secret` 由服务器管理员通过应用注册 UI 输入,绝不会提交到你的仓库。
|
||||
* 请使用 `serverVariables`(而非 `applicationVariables`)——OAuth 凭据是服务器范围的,并且每个 Twenty 服务器只配置一个 OAuth 应用。
|
||||
* 在两个 `serverVariables` 都填写之前,每个应用的设置选项卡会显示"需要服务器管理员"的提示,并且"添加连接"按钮将被禁用。
|
||||
* `type: 'oauth'` 是目前唯一受支持的取值。 该判别器具备前向兼容性:未来的类型(`'pat'`、`'api-key'` 等) 将会与 `oauth` 并列新增子配置块。
|
||||
|
||||
The OAuth callback URL your provider needs to whitelist is:
|
||||
你的提供方需要加入白名单的 OAuth 回调 URL 为:
|
||||
|
||||
```
|
||||
https://<your-twenty-server>/apps/oauth/callback
|
||||
@@ -88,9 +88,9 @@ https://<your-twenty-server>/apps/oauth/callback
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="listConnections / getConnection" description="Use connections from a logic function">
|
||||
<Accordion title="listConnections / getConnection" description="在逻辑函数中使用连接">
|
||||
|
||||
Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens.
|
||||
在逻辑函数处理器内,`listConnections({ providerName })` 会返回此应用针对给定提供方的 `ConnectedAccount` 行,并附带已刷新的访问令牌。
|
||||
|
||||
```ts src/logic-functions/handlers/create-linear-issue-handler.ts
|
||||
import { listConnections } from 'twenty-sdk/logic-function';
|
||||
@@ -135,34 +135,34 @@ export const createLinearIssueHandler = async (input: {
|
||||
};
|
||||
```
|
||||
|
||||
Each connection has:
|
||||
每个连接包含:
|
||||
|
||||
| Field | Description |
|
||||
| ----------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one |
|
||||
| `visibility` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) |
|
||||
| `scopes` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) |
|
||||
| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers |
|
||||
| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) |
|
||||
| `name` / `handle` | The connection's display name (auto-derived at OAuth callback, user-renameable) |
|
||||
| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect |
|
||||
| 字段 | 描述 |
|
||||
| ----------------- | ---------------------------------------------------- |
|
||||
| `id` | 唯一的行 id;传给 `getConnection(id)` 以重新获取单个连接 |
|
||||
| `visibility` | `'user'`(仅对单个工作区成员私有)或 `'workspace'`(与所有成员共享) |
|
||||
| `scopes` | 上游提供方授予的 OAuth 权限(不同于 `visibility`——两者不相关) |
|
||||
| `userWorkspaceId` | 所有者的 userWorkspace id——在 HTTP 路由触发器中用于选择"请求用户的连接"很有用 |
|
||||
| `accessToken` | 最新的 OAuth 访问令牌(若已过期会自动刷新) |
|
||||
| `name` / `handle` | 连接的显示名称(在 OAuth 回调时自动生成,用户可重命名) |
|
||||
| `authFailedAt` | 当最近一次刷新失败时会设置;用户必须重新连接 |
|
||||
|
||||
Key points:
|
||||
关键点:
|
||||
|
||||
* Pass `{ providerName }` to filter by provider; omit it to get all connections this app owns across all providers.
|
||||
* The server transparently refreshes the access token before returning. Your handler always sees a usable token (or `authFailedAt` set).
|
||||
* `getConnection(id)` is the single-row equivalent.
|
||||
* 传入 `{ providerName }` 以按提供方筛选;省略它则可获取此应用在所有提供方上的全部连接。
|
||||
* 服务器会在返回前透明地刷新访问令牌。 你的处理器始终会拿到可用的令牌(或已设置 `authFailedAt`)。
|
||||
* `getConnection(id)` 是获取单行记录的对应方法。
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Per-user vs workspace-shared visibility" description="How users choose between private and shared credentials">
|
||||
<Accordion title="按用户与工作区共享的可见性" description="用户如何在私有与共享凭据之间进行选择">
|
||||
|
||||
When a user clicks "Add connection," they're prompted to pick a visibility:
|
||||
当用户点击"添加连接"时,系统会提示其选择可见性:
|
||||
|
||||
* **Just for me** — the credential is private to the connecting user. Any logic function called on their behalf (HTTP-route trigger with `isAuthRequired: true`) sees it; cron triggers and database events do not.
|
||||
* **Workspace shared** — any workspace member can use the credential. Cron / database triggers also see it, since they have no request user.
|
||||
* **仅限我**——该凭据仅对连接的用户私有。 代表其调用的任何逻辑函数(带有 `isAuthRequired: true` 的 HTTP 路由触发器)都可以看到它;Cron 触发器和数据库事件则不可。
|
||||
* **工作区共享**——任何工作区成员都可以使用该凭据。 Cron / 数据库触发器也可以使用它,因为它们没有请求用户。
|
||||
|
||||
Use the right one for each handler:
|
||||
为每个处理器使用合适的类型:
|
||||
|
||||
```ts
|
||||
// HTTP-route trigger — prefer the request user's own connection.
|
||||
@@ -174,19 +174,19 @@ const conn =
|
||||
const conn = connections.find((c) => c.visibility === 'workspace');
|
||||
```
|
||||
|
||||
Multiple connections per (user, provider) are allowed, so the same user can hold "Personal Linear" and "Work Linear" side by side.
|
||||
每个(用户、提供方)允许有多个连接,因此同一用户可以同时拥有"个人 Linear"和"工作 Linear"。
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="One-time provider setup" description="Register your OAuth app with the third-party service">
|
||||
<Accordion title="一次性提供方设置" description="在第三方服务中注册你的 OAuth 应用">
|
||||
|
||||
For each connection provider, the server admin needs to register an OAuth app at the third party first.
|
||||
对于每个连接提供方,服务器管理员需要先在第三方注册一个 OAuth 应用。
|
||||
|
||||
1. Go to the provider's developer settings (e.g. https://linear.app/settings/api/applications/new).
|
||||
2. Set the **Redirect URI** to `\<SERVER_URL>/apps/oauth/callback`.
|
||||
3. Copy the generated **Client ID** and **Client Secret**.
|
||||
4. Open the installed app in Twenty as a server admin → set the values on the corresponding `serverVariables`.
|
||||
5. Workspace members can then add connections from the per-app **Connections** section.
|
||||
1. 前往提供方的开发者设置(例如 https://linear.app/settings/api/applications/new)。
|
||||
2. 将**Redirect URI** 设置为 `\<SERVER_URL>/apps/oauth/callback`。
|
||||
3. 复制生成的**Client ID**和**Client Secret**。
|
||||
4. 以服务器管理员身份在 Twenty 中打开已安装的应用 → 在相应的 `serverVariables` 上设置这些值。
|
||||
5. 之后,工作区成员可以在每个应用的**连接**部分添加连接。
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
title: Logic Functions
|
||||
description: Define server-side TypeScript functions with HTTP, cron, and database event triggers.
|
||||
title: 逻辑函数
|
||||
description: 定义具有 HTTP、cron 和数据库事件触发器的服务端 TypeScript 函数。
|
||||
icon: bolt
|
||||
---
|
||||
|
||||
Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents.
|
||||
逻辑函数是在 Twenty 平台上运行的服务端 TypeScript 函数。 它们可以由 HTTP 请求、cron 调度或数据库事件触发——也可以作为工具暴露给 AI 代理。
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineLogicFunction" description="Define logic functions and their triggers">
|
||||
<Accordion title="defineLogicFunction" description="定义逻辑函数及其触发器">
|
||||
|
||||
Each function file uses `defineLogicFunction()` to export a configuration with a handler and optional triggers.
|
||||
每个函数文件都使用 `defineLogicFunction()` 导出包含处理程序和可选触发器的配置。
|
||||
|
||||
```ts src/logic-functions/createPostCard.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
@@ -50,15 +50,15 @@ export default defineLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
Available trigger types:
|
||||
* **httpRoute**: Exposes your function on an HTTP path and method **under the `/s/` endpoint**:
|
||||
> e.g. `path: '/post-card/create'` is callable at `https://your-twenty-server.com/s/post-card/create`
|
||||
* **cron**: Runs your function on a schedule using a CRON expression.
|
||||
* **databaseEvent**: Runs on workspace object lifecycle events. When the event operation is `updated`, specific fields to listen to can be specified in the `updatedFields` array. If left undefined or empty, any update will trigger the function.
|
||||
> e.g. `person.updated`, `*.created`, `company.*`
|
||||
可用的触发器类型:
|
||||
* **httpRoute**:在 **`/s/` 端点**下通过 HTTP 路径和方法公开你的函数:
|
||||
> 例如 `path: '/post-card/create'` 可在 `https://your-twenty-server.com/s/post-card/create` 调用
|
||||
* **cron**:使用 CRON 表达式按计划运行你的函数。
|
||||
* **databaseEvent**:在工作空间对象生命周期事件上运行。 当事件操作为 `updated` 时,可以在 `updatedFields` 数组中指定要监听的特定字段。 如果未定义或为空,任何更新都会触发该函数。
|
||||
> 例如 `person.updated`、`*.created`、`company.*`
|
||||
|
||||
<Note>
|
||||
You can also manually execute a function using the CLI:
|
||||
你也可以使用 CLI 手动执行函数:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
|
||||
@@ -68,18 +68,18 @@ yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
|
||||
yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
You can watch logs with:
|
||||
你可以通过以下方式查看日志:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty logs
|
||||
```
|
||||
</Note>
|
||||
|
||||
#### Route trigger payload
|
||||
#### 路由触发器负载
|
||||
|
||||
When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the
|
||||
[AWS HTTP API v2 format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html).
|
||||
Import the `RoutePayload` type from `twenty-sdk`:
|
||||
当路由触发器调用你的逻辑函数时,它会接收一个遵循
|
||||
[AWS HTTP API v2 格式](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html)的 `RoutePayload` 对象。
|
||||
从 `twenty-sdk` 导入 `RoutePayload` 类型:
|
||||
|
||||
```ts
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
||||
@@ -92,24 +92,24 @@ const handler = async (event: RoutePayload) => {
|
||||
};
|
||||
```
|
||||
|
||||
The `RoutePayload` type has the following structure:
|
||||
`RoutePayload` 类型具有以下结构:
|
||||
|
||||
| Property | Type | Description | Example |
|
||||
| ---------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `headers` | `Record\<string, string \| undefined>` | HTTP headers (only those listed in `forwardedRequestHeaders`) | see section below |
|
||||
| `queryStringParameters` | `Record\<string, string \| undefined>` | Query string parameters (multiple values joined with commas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
|
||||
| `pathParameters` | `Record\<string, string \| undefined>` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` |
|
||||
| `body` | `object \| null` | Parsed request body (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
|
||||
| `rawBody` | `string \| undefined` | Original UTF-8 request body, before JSON parsing. Useful for verifying HMAC-style webhook signatures (e.g. GitHub's `X-Hub-Signature-256`, Stripe). `undefined` when the runtime did not preserve it. | |
|
||||
| `isBase64Encoded` | `boolean` | Whether the body is base64 encoded | |
|
||||
| `requestContext.http.method` | `string` | HTTP method (GET, POST, PUT, PATCH, DELETE) | |
|
||||
| `requestContext.http.path` | `string` | Raw request path | |
|
||||
| 属性 | 类型 | 描述 | 示例 |
|
||||
| ---------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `headers` | `Record\<string, string \| undefined>` | HTTP 请求头(仅限 `forwardedRequestHeaders` 中列出的那些) | 见下文 |
|
||||
| `queryStringParameters` | `Record\<string, string \| undefined>` | 查询字符串参数(多个值以逗号连接) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
|
||||
| `pathParameters` | `Record\<string, string \| undefined>` | 从路由模式中提取的路径参数 | `/users/:id`,`/users/123` -> `{ id: '123' }` |
|
||||
| `body` | `object \| null` | 已解析的请求体(JSON) | `{ id: 1 }` -> `{ id: 1 }` |
|
||||
| `rawBody` | `string \| undefined` | 在 JSON 解析之前的原始 UTF-8 请求体。 用于验证 HMAC 风格的 Webhook 签名(例如 GitHub 的 `X-Hub-Signature-256`、Stripe)。 当运行时未保留它时为 `undefined`。 | |
|
||||
| `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | |
|
||||
| `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE) | |
|
||||
| `requestContext.http.path` | `string` | 原始请求路径 | |
|
||||
|
||||
|
||||
#### forwardedRequestHeaders
|
||||
|
||||
By default, HTTP headers from incoming requests are **not** passed to your logic function for security reasons.
|
||||
To access specific headers, list them in the `forwardedRequestHeaders` array:
|
||||
出于安全原因,默认**不会**将传入请求的 HTTP 请求头传递给你的逻辑函数。
|
||||
如需访问特定请求头,请在 `forwardedRequestHeaders` 数组中显式列出:
|
||||
|
||||
```ts
|
||||
export default defineLogicFunction({
|
||||
@@ -125,7 +125,7 @@ export default defineLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
In your handler, access the forwarded headers like this:
|
||||
在你的处理程序中,可以这样访问被转发的请求头:
|
||||
|
||||
```ts
|
||||
const handler = async (event: RoutePayload) => {
|
||||
@@ -138,17 +138,17 @@ const handler = async (event: RoutePayload) => {
|
||||
```
|
||||
|
||||
<Note>
|
||||
Header names are normalized to lowercase. Access them using lowercase keys (e.g., `event.headers['content-type']`).
|
||||
请求头名称会被规范化为小写。 请使用小写键访问它们(例如,`event.headers['content-type']`)。
|
||||
</Note>
|
||||
|
||||
#### Exposing a function as an AI tool or workflow action
|
||||
#### 将函数公开为 AI 工具或工作流操作
|
||||
|
||||
Logic functions can be exposed on two surfaces, each with its own trigger:
|
||||
逻辑函数可以在两个入口对外公开,每个入口都有各自的触发器:
|
||||
|
||||
* **`toolTriggerSettings`** — makes the function discoverable by Twenty's AI features (chat, MCP, function calling). Uses standard JSON Schema, the format LLMs natively understand.
|
||||
* **`workflowActionTriggerSettings`** — makes the function appear as a step in the visual workflow builder. Uses Twenty's rich `InputSchema` so the builder can render proper field editors, variable pickers, and labels.
|
||||
* **`toolTriggerSettings`** — 使该函数可被 Twenty 的 AI 功能(chat、MCP、function calling)发现。 使用标准 JSON Schema,LLM 能够原生理解的格式。
|
||||
* **`workflowActionTriggerSettings`** — 使该函数在可视化工作流构建器中显示为一个步骤。 使用 Twenty 丰富的 `InputSchema`,以便构建器可以呈现合适的字段编辑器、变量选择器和标签。
|
||||
|
||||
A function can opt into one, the other, or both. They sit alongside `cronTriggerSettings`, `databaseEventTriggerSettings`, and `httpRouteTriggerSettings` — same pattern, same shape.
|
||||
函数可以选择加入其中一个、另一个,或两者都加入。 它们与 `cronTriggerSettings`、`databaseEventTriggerSettings` 和 `httpRouteTriggerSettings` 并列 — 相同的模式、相同的结构。
|
||||
|
||||
```ts src/logic-functions/enrich-company.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
@@ -182,10 +182,10 @@ export default defineLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
Key points:
|
||||
关键点:
|
||||
|
||||
* A function can mix surfaces — declare both `toolTriggerSettings` and `workflowActionTriggerSettings` to expose it in chat AND in the workflow builder.
|
||||
* `toolTriggerSettings.inputSchema` and `workflowActionTriggerSettings.inputSchema` are both optional. When omitted, the manifest builder infers them from the handler source code (JSON Schema for the AI tool, Twenty's `InputSchema` for the workflow action). Provide one explicitly when you want richer typing — for example, with `FieldMetadataType`-aware fields like `CURRENCY` or `RELATION` for the workflow builder, or with `description` fields the AI agent can read:
|
||||
* 函数可以混用这些入口 — 同时声明 `toolTriggerSettings` 和 `workflowActionTriggerSettings`,即可在 chat 和工作流构建器中同时公开它。
|
||||
* `toolTriggerSettings.inputSchema` 和 `workflowActionTriggerSettings.inputSchema` 均为可选。 如果省略,清单构建器会根据处理器源代码进行推断(AI 工具使用 JSON Schema,工作流操作使用 Twenty 的 `InputSchema`)。 当你需要更丰富的类型时,可显式提供一个 — 例如,在工作流构建器中使用对 `FieldMetadataType` 友好的字段(如 `CURRENCY` 或 `RELATION`),或提供 AI 代理可读取的 `description` 字段:
|
||||
|
||||
```ts
|
||||
export default defineLogicFunction({
|
||||
@@ -210,29 +210,29 @@ export default defineLogicFunction({
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Write a good `description`.** AI agents rely on the function's `description` field to decide when to use the tool. Be specific about what the tool does and when it should be called.
|
||||
**写一个好的 `description`。** AI 智能体会依赖该函数的 `description` 字段来决定何时使用该工具。 明确说明该工具的作用以及应在何时调用。
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
**Install hooks** — pre-install and post-install handlers — share this runtime but are declared with their own define functions and don't take trigger settings. See [Install Hooks](/l/zh/developers/extend/apps/config/install-hooks) for `definePreInstallLogicFunction` and `definePostInstallLogicFunction`.
|
||||
**安装 hooks**——预安装和后安装处理程序——共享此运行时,但使用它们自己的 define 函数进行声明,并且不接受触发器设置。 有关 `definePreInstallLogicFunction` 和 `definePostInstallLogicFunction`,请参阅 [Install Hooks](/l/zh/developers/extend/apps/config/install-hooks)。
|
||||
</Note>
|
||||
|
||||
## Typed API clients (twenty-client-sdk)
|
||||
## 类型化 API 客户端(`twenty-client-sdk`)
|
||||
|
||||
The `twenty-client-sdk` package provides two typed GraphQL clients for interacting with the Twenty API from your logic functions and front components.
|
||||
`twenty-client-sdk` 包提供了两个类型化的 GraphQL 客户端,供你的逻辑函数和前端组件与 Twenty API 交互。
|
||||
|
||||
| Client | Import | Endpoint | Generated? |
|
||||
| ------------------- | ---------------------------- | ---------------------------------------------- | ---------------------- |
|
||||
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — workspace data (records, objects) | Yes, at dev/build time |
|
||||
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — workspace config, file uploads | No, ships pre-built |
|
||||
| 客户端 | 导入 | 端点 | 是否生成? |
|
||||
| ------------------- | ---------------------------- | ------------------------ | --------- |
|
||||
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql`——工作区数据(记录、对象) | 是,在开发/构建时 |
|
||||
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata`——工作区配置、文件上传 | 否,已预构建提供 |
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="CoreApiClient" description="Query and mutate workspace data (records, objects)">
|
||||
<Accordion title="CoreApiClient" description="查询和变更工作区数据(记录、对象)">
|
||||
|
||||
`CoreApiClient` is the main client for querying and mutating workspace data. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields.
|
||||
`CoreApiClient` 是用于查询和变更工作区数据的主要客户端。 它会在执行 `yarn twenty dev` 或 `yarn twenty build` 时**根据你的工作区架构生成**,因此完全类型化以匹配你的对象和字段。
|
||||
|
||||
```ts
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
@@ -269,15 +269,15 @@ const { createCompany } = await client.mutation({
|
||||
});
|
||||
```
|
||||
|
||||
The client uses a selection-set syntax: pass `true` to include a field, use `__args` for arguments, and nest objects for relations. You get full autocompletion and type checking based on your workspace schema.
|
||||
该客户端使用选择集语法:传入 `true` 以包含某字段,使用 `__args` 传递参数,并通过嵌套对象表示关系。 你将基于工作区架构获得完整的自动补全和类型检查。
|
||||
|
||||
<Note>
|
||||
**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`.
|
||||
**CoreApiClient 在开发/构建时生成。** 如果在未先运行 `yarn twenty dev` 或 `yarn twenty build` 的情况下尝试使用它,将会抛出错误。 该生成过程是自动完成的——CLI 会自省你的工作区 GraphQL 架构,并使用 `@genql/cli` 生成类型化客户端。
|
||||
</Note>
|
||||
|
||||
#### Using CoreSchema for type annotations
|
||||
#### 使用 CoreSchema 进行类型标注
|
||||
|
||||
`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters:
|
||||
`CoreSchema` 提供与工作区对象相匹配的 TypeScript 类型,可用于为组件状态或函数参数进行类型标注:
|
||||
|
||||
```ts
|
||||
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
|
||||
@@ -299,9 +299,9 @@ setCompany(result.company);
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="MetadataApiClient" description="Workspace config, applications, and file uploads">
|
||||
<Accordion title="MetadataApiClient" description="工作区配置、应用和文件上传">
|
||||
|
||||
`MetadataApiClient` ships pre-built with the SDK (no generation required). It queries the `/metadata` endpoint for workspace configuration, applications, and file uploads.
|
||||
`MetadataApiClient` 随 SDK 一并提供,已预构建(无需生成)。 它会查询 `/metadata` 端点以获取工作区配置、应用和文件上传。
|
||||
|
||||
```ts
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
@@ -328,9 +328,9 @@ const { objects } = await metadataClient.query({
|
||||
});
|
||||
```
|
||||
|
||||
#### Uploading files
|
||||
#### 上传文件
|
||||
|
||||
`MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields:
|
||||
`MetadataApiClient` 包含一个 `uploadFile` 方法,用于将文件附加到文件类型字段:
|
||||
|
||||
```ts
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
@@ -351,25 +351,25 @@ console.log(uploadedFile);
|
||||
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ---------------------------------- | -------- | --------------------------------------------------------------- |
|
||||
| `fileBuffer` | `Buffer` | The raw file contents |
|
||||
| `filename` | `string` | The name of the file (used for storage and display) |
|
||||
| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) |
|
||||
| `fieldMetadataUniversalIdentifier` | `string` | The `universalIdentifier` of the file-type field on your object |
|
||||
| 参数 | 类型 | 描述 |
|
||||
| ---------------------------------- | -------- | -------------------------------------------- |
|
||||
| `fileBuffer` | `Buffer` | 原始文件内容 |
|
||||
| `filename` | `string` | 文件名称(用于存储和显示) |
|
||||
| `contentType` | `string` | MIME 类型(如果省略,默认为 `application/octet-stream`) |
|
||||
| `fieldMetadataUniversalIdentifier` | `string` | 你的对象上文件类型字段的 `universalIdentifier` |
|
||||
|
||||
Key points:
|
||||
* Uses the field's `universalIdentifier` (not its workspace-specific ID), so your upload code works across any workspace where your app is installed.
|
||||
* The returned `url` is a signed URL you can use to access the uploaded file.
|
||||
关键点:
|
||||
* 使用字段的 `universalIdentifier`(而不是其工作区特定的 ID),因此你的上传代码可在安装了你的应用的任何工作区中运行。
|
||||
* 返回的 `url` 是一个签名 URL,你可以用它来访问已上传的文件。
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
When your code runs on Twenty (logic functions or front components), the platform injects credentials as environment variables:
|
||||
当你的代码在 Twenty 上运行(逻辑函数或前端组件)时,平台会以环境变量的形式注入凭据:
|
||||
|
||||
* `TWENTY_API_URL` — Base URL of the Twenty API
|
||||
* `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role
|
||||
* `TWENTY_API_URL`——Twenty API 的基础 URL
|
||||
* `TWENTY_APP_ACCESS_TOKEN`——作用域限定为你的应用默认函数角色的短期密钥
|
||||
|
||||
You do **not** need to pass these to the clients — they read from `process.env` automatically. The API key's permissions are determined by the role referenced in `defaultRoleUniversalIdentifier` in your `application-config.ts`.
|
||||
你无需将这些值传递给客户端——它们会自动从 `process.env` 读取。 API 密钥的权限由你的 `application-config.ts` 中 `defaultRoleUniversalIdentifier` 引用的角色决定。
|
||||
</Note>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Server-side TypeScript that runs inside Twenty — triggered by HTTP routes, cron schedules, database events, AI tools, or workflow actions.
|
||||
title: 概览
|
||||
description: 在 Twenty 内运行的服务端 TypeScript,可由 HTTP 路由、cron 调度、数据库事件、AI 工具或工作流操作触发。
|
||||
icon: bolt
|
||||
---
|
||||
|
||||
A Twenty app's **logic layer** is the code that *runs* — server-side TypeScript handlers reacting to HTTP requests, cron schedules, and record changes; AI skills and agents that live inside the workspace; and OAuth connections that let your functions act on a user's behalf in third-party services.
|
||||
Twenty 应用的 **逻辑层** 是实际*运行*的代码——用于响应 HTTP 请求、cron 调度和记录变更的服务端 TypeScript 处理程序;存在于工作区内的 AI 技能和智能体;以及让你的函数能够代表用户在第三方服务中执行操作的 OAuth 连接。
|
||||
|
||||
```text
|
||||
┌─ HTTP route ──┐
|
||||
@@ -22,34 +22,34 @@ A Twenty app's **logic layer** is the code that *runs* — server-side TypeScrip
|
||||
└────────────────────────────┘
|
||||
```
|
||||
|
||||
## In this section
|
||||
## 本节内容
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Logic Functions" icon="bolt" href="/l/zh/developers/extend/apps/logic/logic-functions">
|
||||
The core building block — trigger types, payloads, and the typed API client.
|
||||
<Card title="逻辑函数" icon="bolt" href="/l/zh/developers/extend/apps/logic/logic-functions">
|
||||
核心构建块——触发器类型、载荷以及类型化的 API 客户端。
|
||||
</Card>
|
||||
<Card title="Skills & Agents" icon="robot" href="/l/zh/developers/extend/apps/logic/skills-and-agents">
|
||||
Reusable AI agent instructions and assistants with custom system prompts.
|
||||
<Card title="技能与智能体" icon="robot" href="/l/zh/developers/extend/apps/logic/skills-and-agents">
|
||||
可复用的 AI 智能体指令和助手,并支持自定义 system 提示词。
|
||||
</Card>
|
||||
<Card title="Connections" icon="plug" href="/l/zh/developers/extend/apps/logic/connections">
|
||||
OAuth credentials your app holds for third-party services — Linear, GitHub, Slack, and more.
|
||||
<Card title="连接" icon="plug" href="/l/zh/developers/extend/apps/logic/connections">
|
||||
你的应用为第三方服务(如 Linear、GitHub、Slack 等)持有的 OAuth 凭证。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Trigger types at a glance
|
||||
## 触发器类型一览
|
||||
|
||||
A logic function picks one or more triggers — every entry below is a separate field on `defineLogicFunction()`:
|
||||
逻辑函数选择一个或多个触发器——下面的每一项都是 `defineLogicFunction()` 上的一个独立字段:
|
||||
|
||||
| Trigger | When it runs | Setting |
|
||||
| ------------------- | ---------------------------------------------------------- | ------------------------------- |
|
||||
| **HTTP route** | A request hits your `/s/\<path>` endpoint | `httpRouteTriggerSettings` |
|
||||
| **Cron** | A CRON expression matches | `cronTriggerSettings` |
|
||||
| **Database event** | A workspace record is created, updated, or deleted | `databaseEventTriggerSettings` |
|
||||
| **AI tool** | A Twenty AI feature decides to call your function | `toolTriggerSettings` |
|
||||
| **Workflow action** | A workflow step invokes your function | `workflowActionTriggerSettings` |
|
||||
| 触发器 | 触发时机 | 设置 |
|
||||
| ----------- | --------------------------------------- | ------------------------------- |
|
||||
| **HTTP 路由** | 请求命中你的 `/s/\<path>` 端点 | `httpRouteTriggerSettings` |
|
||||
| **Cron** | 匹配到一个 CRON 表达式时 | `cronTriggerSettings` |
|
||||
| **数据库事件** | 当工作区记录被创建、更新或删除时 | `databaseEventTriggerSettings` |
|
||||
| **AI 工具** | 某个 Twenty AI 功能决定调用你的函数时 | `toolTriggerSettings` |
|
||||
| **工作流动作** | 当工作流步骤调用你的函数时 | `workflowActionTriggerSettings` |
|
||||
|
||||
Functions run sandboxed in isolated Node.js processes and access the workspace through a typed API client scoped to the role declared on [`defineApplication()`](/l/zh/developers/extend/apps/config/application).
|
||||
函数在隔离的 Node.js 进程沙箱中运行,并通过限定在 [`defineApplication()`](/l/zh/developers/extend/apps/config/application) 上声明角色范围内的类型化 API 客户端访问工作区。
|
||||
|
||||
<Note>
|
||||
**Install-time hooks** — code that runs before or after the install — share this runtime but use their own define functions and live under [Config → Install Hooks](/l/zh/developers/extend/apps/config/install-hooks).
|
||||
**安装时钩子(Install-time hooks)**——在安装前或安装后运行的代码——与上述共享同一运行时,但使用它们自己的 define 函数,并位于 [Config → Install Hooks](/l/zh/developers/extend/apps/config/install-hooks) 下。
|
||||
</Note>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
title: Skills & Agents
|
||||
description: Define AI skills and agents for your app.
|
||||
title: 技能与智能体
|
||||
description: 为你的应用定义 AI 技能和智能体。
|
||||
icon: robot
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Skills and agents are currently in alpha. The feature works but is still evolving.
|
||||
技能和智能体目前处于 Alpha 阶段。 该功能可用,但仍在演进中。
|
||||
</Warning>
|
||||
|
||||
Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts.
|
||||
应用可以定义存在于工作区内的 AI 能力——可复用的技能指令以及具有自定义系统提示词的智能体。
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineSkill" description="Define AI agent skills">
|
||||
<Accordion title="defineSkill" description="定义 AI 智能体技能">
|
||||
|
||||
Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation:
|
||||
技能定义了可复用的指令和能力,AI 智能体可在你的工作区中使用。 使用 `defineSkill()` 定义带内置校验的技能:
|
||||
|
||||
```ts src/skills/example-skill.ts
|
||||
import { defineSkill } from 'twenty-sdk/define';
|
||||
@@ -32,17 +32,17 @@ export default defineSkill({
|
||||
});
|
||||
```
|
||||
|
||||
Key points:
|
||||
* `name` is a unique identifier string for the skill (kebab-case recommended).
|
||||
* `label` is the human-readable display name shown in the UI.
|
||||
* `content` contains the skill instructions — this is the text the AI agent uses.
|
||||
* `icon` (optional) sets the icon displayed in the UI.
|
||||
* `description` (optional) provides additional context about the skill's purpose.
|
||||
关键点:
|
||||
* `name` 是该技能的唯一标识字符串(推荐使用 kebab-case)。
|
||||
* `label` 是在 UI 中显示的人类可读名称。
|
||||
* `content` 包含技能指令——这是 AI 智能体使用的文本。
|
||||
* `icon`(可选)设置在 UI 中显示的图标。
|
||||
* `description`(可选)提供有关技能用途的更多上下文。
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineAgent" description="Define AI agents with custom prompts">
|
||||
<Accordion title="defineAgent" description="使用自定义提示词定义 AI 智能体">
|
||||
|
||||
Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt:
|
||||
智能体是在你的工作区内驻留的 AI 助手。 使用 `defineAgent()` 来创建带有自定义系统提示词的智能体:
|
||||
|
||||
```ts src/agents/example-agent.ts
|
||||
import { defineAgent } from 'twenty-sdk/define';
|
||||
@@ -57,13 +57,13 @@ export default defineAgent({
|
||||
});
|
||||
```
|
||||
|
||||
Key points:
|
||||
* `name` is the unique identifier string for the agent (kebab-case recommended).
|
||||
* `label` is the display name shown in the UI.
|
||||
* `prompt` is the system prompt that defines the agent's behavior.
|
||||
* `description` (optional) provides context about what the agent does.
|
||||
* `icon` (optional) sets the icon displayed in the UI.
|
||||
* `modelId` (optional) overrides the default AI model used by the agent.
|
||||
关键点:
|
||||
* `name` 是该智能体的唯一标识字符串(推荐使用 kebab-case)。
|
||||
* `label` 是在 UI 中显示的名称。
|
||||
* `prompt` 是定义智能体行为的系统提示词。
|
||||
* `description`(可选)提供有关智能体功能的上下文。
|
||||
* `icon`(可选)设置在 UI 中显示的图标。
|
||||
* `modelId`(可选)会覆盖该智能体使用的默认 AI 模型。
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: CLI
|
||||
description: yarn twenty commands for executing functions, streaming logs, managing app installations, and switching remotes.
|
||||
description: yarn twenty 命令可用于执行函数、流式传输日志、管理应用安装以及切换远程仓库。
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
Beyond `dev`, `build`, `add`, and `typecheck`, the `yarn twenty` CLI provides commands for executing functions, viewing logs, and managing app installations.
|
||||
除了 `dev`、`build`、`add` 和 `typecheck` 外,`yarn twenty` CLI 还提供了用于执行函数、查看日志和管理应用安装的命令。
|
||||
|
||||
## Executing functions (`yarn twenty exec`)
|
||||
## 执行函数(`yarn twenty exec`)
|
||||
|
||||
Run a logic function manually without triggering it via HTTP, cron, or database event:
|
||||
手动运行逻辑函数,而无需通过 HTTP、定时任务或数据库事件来触发:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Execute by function name
|
||||
@@ -24,9 +24,9 @@ yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}'
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
## Viewing function logs (`yarn twenty logs`)
|
||||
## 查看函数日志(`yarn twenty logs`)
|
||||
|
||||
Stream execution logs for your app's logic functions:
|
||||
实时流式查看你的应用逻辑函数的执行日志:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Stream all function logs
|
||||
@@ -40,12 +40,12 @@ yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
<Note>
|
||||
This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server.
|
||||
这与 `yarn twenty server logs` 不同,后者显示的是 Docker 容器日志。 `yarn twenty logs` 会显示来自 Twenty 服务器的应用函数执行日志。
|
||||
</Note>
|
||||
|
||||
## Uninstalling an app (`yarn twenty uninstall`)
|
||||
## 卸载应用(`yarn twenty uninstall`)
|
||||
|
||||
Remove your app from the active workspace:
|
||||
将你的应用从活动工作区中移除:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty uninstall
|
||||
@@ -54,9 +54,9 @@ yarn twenty uninstall
|
||||
yarn twenty uninstall --yes
|
||||
```
|
||||
|
||||
## Managing remotes
|
||||
## 管理远程
|
||||
|
||||
A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time.
|
||||
“远程”是指你的应用连接到的 Twenty 服务器。 在设置期间,脚手架工具会为你自动创建一个。 你可以随时添加更多远程或在它们之间切换。
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
@@ -75,4 +75,4 @@ yarn twenty remote list
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Your credentials are stored in `~/.twenty/config.json`.
|
||||
你的凭据存储在 `~/.twenty/config.json` 中。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Build, test, and ship your app — CLI commands, integration tests, CI, and publishing to a server or to npm.
|
||||
title: 概览
|
||||
description: 构建、测试并发布你的应用——CLI 命令、集成测试、CI,以及发布到服务器或 npm。
|
||||
icon: rocket
|
||||
---
|
||||
|
||||
The **operations layer** is everything you do *to* your app rather than *with* it: invoking CLI commands, running integration tests against a real Twenty server, configuring CI, and shipping releases — either as a tarball deployed to a single server or as an npm package listed in the marketplace.
|
||||
**运维层**指的是你对应用*进行*而不是*使用*它所做的一切:调用 CLI 命令、针对真实的 Twenty 服务器运行集成测试、配置 CI,以及发布发行版——可以是部署到单个服务器的 tarball,也可以是发布到应用市场中列出的 npm 包。
|
||||
|
||||
```text
|
||||
develop ─▶ test ─▶ build ─▶ deploy / publish
|
||||
@@ -14,16 +14,16 @@ The **operations layer** is everything you do *to* your app rather than *with* i
|
||||
dev build yarn twenty publish (npm → marketplace)
|
||||
```
|
||||
|
||||
## In this section
|
||||
## 本节内容
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CLI" icon="terminal" href="/l/zh/developers/extend/apps/operations/cli">
|
||||
`yarn twenty` reference — exec, logs, uninstall, remotes.
|
||||
`yarn twenty` 参考——exec、logs、uninstall、remotes。
|
||||
</Card>
|
||||
<Card title="Testing" icon="flask" href="/l/zh/developers/extend/apps/operations/testing">
|
||||
Vitest setup, integration tests, type checking, CI workflow.
|
||||
<Card title="测试" icon="flask" href="/l/zh/developers/extend/apps/operations/testing">
|
||||
Vitest 配置、集成测试、类型检查、CI 工作流。
|
||||
</Card>
|
||||
<Card title="Publishing" icon="upload" href="/l/zh/developers/extend/apps/operations/publishing">
|
||||
Build, deploy a tarball, publish to npm, install.
|
||||
<Card title="发布" icon="上传" href="/l/zh/developers/extend/apps/operations/publishing">
|
||||
构建、部署 tarball、发布到 npm、安装。
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
---
|
||||
title: Publishing
|
||||
icon: upload
|
||||
description: Distribute your Twenty app to the marketplace or deploy it internally.
|
||||
title: 发布
|
||||
icon: 上传
|
||||
description: 将你的 Twenty 应用分发到应用市场,或进行内部部署。
|
||||
---
|
||||
|
||||
## Overview
|
||||
## 概览
|
||||
|
||||
Once your app is [built and tested locally](/l/zh/developers/extend/apps/getting-started/concepts), you have two paths for distributing it:
|
||||
一旦你的应用已[在本地构建并完成测试](/l/zh/developers/extend/apps/getting-started/concepts),你可以通过两种方式进行分发:
|
||||
|
||||
* **Deploy a tarball** — upload your app directly to a specific Twenty server for internal or private use.
|
||||
* **Publish to npm** — list your app in the Twenty marketplace for any workspace to discover and install.
|
||||
* **部署 tar 包** — 直接将你的应用上传到特定的 Twenty 服务器,以供内部或私有使用。
|
||||
* **发布到 npm** — 将你的应用在 Twenty 应用市场上架,供任何工作区发现并安装。
|
||||
|
||||
Both paths start from the same **build** step.
|
||||
两种路径都从同一个**构建**步骤开始。
|
||||
|
||||
## Building your app
|
||||
## 构建你的应用
|
||||
|
||||
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
|
||||
运行构建命令来编译你的应用,并生成适用于分发的 `manifest.json`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
|
||||
这会编译 TypeScript 源码,转译逻辑函数和前端组件,并将所有内容写入 `.twenty/output/`。 添加 `--tarball`,还可以生成用于手动分发或 deploy 命令的 `.tgz` 包。
|
||||
|
||||
## Deploying to a server (tarball)
|
||||
## 部署到服务器(tar 包)
|
||||
|
||||
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can deploy a tarball directly to a Twenty server.
|
||||
对于你不希望公开的应用(专有工具、仅供企业使用的集成或实验性构建),你可以将 tar 包直接部署到某台 Twenty 服务器。
|
||||
|
||||
### Prerequisites
|
||||
### 先决条件
|
||||
|
||||
Before deploying, you need a configured remote pointing to the target server. Remotes store the server URL and authentication credentials locally in `~/.twenty/config.json`.
|
||||
在部署之前,你需要配置一个指向目标服务器的远程。 远程会将服务器 URL 和身份验证凭据本地存储在 `~/.twenty/config.json` 中。
|
||||
|
||||
Add a remote:
|
||||
添加远程:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Deploying
|
||||
### 部署
|
||||
|
||||
Build and upload your app to the server in one step:
|
||||
一步构建并将你的应用上传到服务器:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
@@ -47,39 +47,39 @@ yarn twenty deploy
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Sharing a deployed app
|
||||
### 共享已部署的应用
|
||||
|
||||
<Warning>
|
||||
Sharing private (tarball) apps across workspaces is an **Enterprise** feature. The **Distribution** tab will show an upgrade prompt instead of the share controls until your workspace has a valid Enterprise key. See [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to activate it.
|
||||
在多个工作区之间共享私有(tarball)应用是一项 **Enterprise** 功能。 在您的工作区拥有有效的 Enterprise 密钥之前,**Distribution** 选项卡将显示升级提示,而不是共享控件。 请前往 [设置 > 管理面板 > Enterprise](/settings/admin-panel#enterprise) 以启用。
|
||||
</Warning>
|
||||
|
||||
Tarball apps are not listed in the public marketplace, so other workspaces on the same server won't discover them by browsing. Once your workspace is on the Enterprise plan, you can share a deployed app like this:
|
||||
通过 tar 包分发的应用不会出现在公共市场中,因此同一服务器上的其他工作区无法通过浏览发现它们。 一旦您的工作区升级到企业版计划,您就可以像这样分享已部署的应用:
|
||||
|
||||
1. Go to **Settings > Applications > Registrations** and open your app
|
||||
2. In the **Distribution** tab, click **Copy share link**
|
||||
3. Share this link with users on other workspaces — it takes them directly to the app's install page
|
||||
1. 前往 **Settings > Applications > Registrations** 并打开你的应用
|
||||
2. 在 **Distribution** 选项卡中,点击 **Copy share link**
|
||||
3. 将此链接分享给其他工作区的用户 — 它会将他们直接带到该应用的安装页面
|
||||
|
||||
The share link uses the server's base URL (without any workspace subdomain) so it works for any workspace on the server.
|
||||
该分享链接使用服务器的基础 URL(不包含任何工作区子域),因此适用于该服务器上的任意工作区。
|
||||
|
||||
### Version management
|
||||
### 版本管理
|
||||
|
||||
When updating an already deployed tarball app, the server requires the `version` in `package.json` to be **strictly higher** (per [semver](https://semver.org) ordering) than the currently deployed version. Re-deploying the same version, or pushing a lower one, is rejected before the tarball is stored — you'll see a `VERSION_ALREADY_EXISTS` error from the CLI.
|
||||
在更新已部署的 tarball 应用时,服务器要求 `package.json` 中的 `version` 必须**严格高于**(按[语义化版本](https://semver.org)排序)当前已部署的版本。 在 tar 包存储之前,重新部署相同版本或推送更低版本都会被拒绝 — 你会在 CLI 中看到 `VERSION_ALREADY_EXISTS` 错误。
|
||||
|
||||
To release an update:
|
||||
要发布更新:
|
||||
|
||||
1. Bump the `version` field in your `package.json` (e.g. `1.2.3` → `1.2.4`, `1.3.0`, or `2.0.0`)
|
||||
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
|
||||
3. Workspaces that have the app installed will see the upgrade available in their settings
|
||||
1. 将 `package.json` 中的 `version` 字段递增(例如:`1.2.3` → `1.2.4`、`1.3.0` 或 `2.0.0`)
|
||||
2. 运行 `yarn twenty deploy`(或 `yarn twenty deploy --remote production`)
|
||||
3. 已安装该应用的工作区会在其设置中看到可用的升级
|
||||
|
||||
<Note>
|
||||
Pre-release tags work as expected: bumping `1.0.0-rc.1` → `1.0.0-rc.2` is allowed, and a final release like `1.0.0` is correctly recognized as higher than `1.0.0-rc.5`. The version in `package.json` must itself be a valid semver string.
|
||||
预发布标签按预期工作:将 `1.0.0-rc.1` 递增为 `1.0.0-rc.2` 是允许的,并且像 `1.0.0` 这样的正式发布会被正确识别为高于 `1.0.0-rc.5`。 `package.json` 中的版本本身必须是有效的 SemVer 字符串。
|
||||
</Note>
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
### Server version compatibility
|
||||
### 服务器版本兼容性
|
||||
|
||||
If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`:
|
||||
如果你的应用使用了特定 Twenty 服务器版本中引入的功能(例如在 v2.3.0 中新增的 OAuth 提供方),应当在 `package.json` 的 `engines.twenty` 字段中声明应用所需的最低服务器版本:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -92,83 +92,83 @@ If your app uses a feature introduced in a specific Twenty server version (for e
|
||||
}
|
||||
```
|
||||
|
||||
The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns:
|
||||
该值是标准的 [semver 范围](https://github.com/npm/node-semver#ranges)。 常见模式:
|
||||
|
||||
| Range | Meaning |
|
||||
| ---------------------------------- | ------------------------------------------ |
|
||||
| `>=2.3.0` | Any server from 2.3.0 onward |
|
||||
| `>=2.3.0 \<3.0.0` | 2.3.0 or later, but below the next major |
|
||||
| `^2.3.0` | Same as `>=2.3.0 \<3.0.0` |
|
||||
| 范围 | 含义 |
|
||||
| ---------------------------------- | --------------------------------------- |
|
||||
| `>=2.3.0` | 任何 2.3.0 及以上的服务器 |
|
||||
| `>=2.3.0 \<3.0.0` | 2.3.0 或更高,但低于下一个主版本 |
|
||||
| `^2.3.0` | 与 `>=2.3.0 \<3.0.0` 相同 |
|
||||
|
||||
**What happens at deploy and install time:**
|
||||
**在部署和安装时会发生什么:**
|
||||
|
||||
* If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version.
|
||||
* If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps).
|
||||
* If the server has no `APP_VERSION` configured, the check is skipped.
|
||||
* 如果已设置 `engines.twenty`,且目标服务器的版本不满足该范围,则部署(tarball 上传)或安装将被拒绝,并返回 `SERVER_VERSION_INCOMPATIBLE` 错误以及一条同时指明所需范围和实际服务器版本的消息。
|
||||
* 如果 `engines.twenty` **未设置**,则该应用可在任何服务器版本上被接受(与现有应用向后兼容)。
|
||||
* 如果服务器未配置 `APP_VERSION`,则跳过该检查。
|
||||
|
||||
<Note>
|
||||
The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility.
|
||||
服务器是权威校验方——它会在 tarball 上传和工作区安装时验证 `engines.twenty`。 即使你通过带外方式部署 tarball 或从应用市场安装,服务器仍会强制执行兼容性要求。
|
||||
</Note>
|
||||
|
||||
## Automated CI/CD (scaffolded workflows)
|
||||
## 自动化 CI/CD(脚手架生成的工作流)
|
||||
|
||||
Apps generated with `create-twenty-app` ship with two GitHub Actions workflows out of the box, under `.github/workflows/`. They are ready to run as soon as you push the repo to GitHub — no extra setup is needed for CI, and CD only requires a single secret.
|
||||
使用 `create-twenty-app` 生成的应用开箱即带有两个 GitHub Actions 工作流,位于 `.github/workflows/`。 当你将仓库推送到 GitHub 后即可运行——CI 无需额外设置,CD 只需要一个机密。
|
||||
|
||||
### CI — `ci.yml`
|
||||
|
||||
Runs integration tests on every push to `main` and every pull request.
|
||||
它会在每次向 `main` 推送以及拉取请求上自动运行你的集成测试。
|
||||
|
||||
**What it does:**
|
||||
**作用:**
|
||||
|
||||
1. Checks out your app's source.
|
||||
2. Spawns an isolated Twenty test instance using the `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` composite action (the CI equivalent of `yarn twenty server start --test`).
|
||||
3. Enables Corepack, sets up Node.js from your `.nvmrc`, and installs dependencies with `yarn install --immutable`.
|
||||
4. Runs `yarn test`, passing `TWENTY_API_URL` and `TWENTY_API_KEY` from the spawned instance so your tests can talk to a real server.
|
||||
1. 检出你的应用源代码。
|
||||
2. 使用 `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` 组合 action 启动一个隔离的 Twenty 测试实例(相当于 CI 中的 `yarn twenty server start --test`)。
|
||||
3. 启用 Corepack,从你的 `.nvmrc` 设置 Node.js,并使用 `yarn install --immutable` 安装依赖。
|
||||
4. 运行 `yarn test`,并从启动的实例传入 `TWENTY_API_URL` 和 `TWENTY_API_KEY`,以便你的测试可以与真实服务器通信。
|
||||
|
||||
**Config knobs:**
|
||||
**配置选项:**
|
||||
|
||||
* `TWENTY_VERSION` (env, defaults to `latest`) — pin the Twenty server version used in CI by editing this in `ci.yml`.
|
||||
* Concurrency is grouped by `github.ref` and cancels in-progress runs on new pushes.
|
||||
* `TWENTY_VERSION`(环境变量,默认 `latest`)— 通过在 `ci.yml` 中编辑它来固定 CI 使用的 Twenty 服务器版本。
|
||||
* 并发按 `github.ref` 分组,并会在有新的推送时取消进行中的运行。
|
||||
|
||||
No secrets are required — the test instance is ephemeral and lives only for the duration of the job.
|
||||
不需要任何机密——测试实例是临时的,只在作业持续期间存在。
|
||||
|
||||
### CD — `cd.yml`
|
||||
|
||||
Deploys your app to a configured Twenty server on every push to `main`, and optionally from a pull request when the `deploy` label is applied.
|
||||
在每次向 `main` 推送时将你的应用部署到已配置的 Twenty 服务器;当为拉取请求添加 `deploy` 标签时,也可从该拉取请求进行部署。
|
||||
|
||||
**What it does:**
|
||||
**作用:**
|
||||
|
||||
1. Checks out the PR head (for labeled PRs) or the pushed commit.
|
||||
2. Runs `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — the CI equivalent of `yarn twenty deploy`.
|
||||
3. Runs `twentyhq/twenty/.github/actions/install-twenty-app@main` so the newly deployed version is installed into the target workspace.
|
||||
1. 检出 PR 的 head(针对已加标签的 PR),或被推送的提交。
|
||||
2. 运行 `twentyhq/twenty/.github/actions/deploy-twenty-app@main`——相当于 CI 中的 `yarn twenty deploy`。
|
||||
3. 运行 `twentyhq/twenty/.github/actions/install-twenty-app@main`,将新部署的版本安装到目标工作区。
|
||||
|
||||
**Required configuration:**
|
||||
**必需的配置:**
|
||||
|
||||
| Setting | Where | Purpose |
|
||||
| ----------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `TWENTY_DEPLOY_URL` | `env` in `cd.yml` (defaults to `http://localhost:3000`) | The Twenty server to deploy to. Change this to your real server URL before first use. |
|
||||
| `TWENTY_DEPLOY_API_KEY` | GitHub repo **Settings → Secrets and variables → Actions** | API key with deploy permission on the target server. |
|
||||
| 设置 | 位置 | 目的 |
|
||||
| ----------------------- | -------------------------------------------------------- | ---------------------------------------- |
|
||||
| `TWENTY_DEPLOY_URL` | `cd.yml` 中的 `env`(默认值为 `http://localhost:3000`) | 要部署到的 Twenty 服务器。 首次使用前将其更改为你真实的服务器 URL。 |
|
||||
| `TWENTY_DEPLOY_API_KEY` | GitHub 仓库 **Settings → Secrets and variables → Actions** | 在目标服务器上具有部署权限的 API 密钥。 |
|
||||
|
||||
<Note>
|
||||
The default `TWENTY_DEPLOY_URL` of `http://localhost:3000` is a placeholder — it will not reach anything from a GitHub-hosted runner. Update it to your server's public URL (or use a self-hosted runner with network access) before enabling CD.
|
||||
默认的 `TWENTY_DEPLOY_URL` 值 `http://localhost:3000` 只是占位符——从 GitHub 托管的 runner 无法访问任何资源。 在启用 CD 之前,将其更新为你服务器的公网 URL(或使用具有网络访问权限的自托管 runner)。
|
||||
</Note>
|
||||
|
||||
**Triggering a preview deploy from a PR:**
|
||||
**从 PR 触发预览部署:**
|
||||
|
||||
Add the `deploy` label to a pull request. The `if:` guard in `cd.yml` will run the job for that PR using the PR's head commit, letting you validate a change on the target server before merging.
|
||||
为拉取请求添加 `deploy` 标签。 在 `cd.yml` 中的 `if:` 守卫会使用该 PR 的 head 提交为其运行作业,使你能在合并前在目标服务器上验证更改。
|
||||
|
||||
### Pinning the reusable actions
|
||||
### 固定可复用的 actions
|
||||
|
||||
Both workflows reference reusable actions at `@main`, so action updates in the `twentyhq/twenty` repo are picked up automatically. If you want deterministic builds, replace `@main` with a commit SHA or release tag on each `uses:` line.
|
||||
两个工作流都引用了位于 `@main` 的可复用 actions,因此会自动获取 `twentyhq/twenty` 仓库中的 action 更新。 如果你希望构建具有确定性,请在每个 `uses:` 行中将 `@main` 替换为某个提交的 SHA 或发行标签。
|
||||
|
||||
## Publishing to npm
|
||||
## 发布到 npm
|
||||
|
||||
Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twenty workspace can browse, install, and upgrade marketplace apps directly from the UI.
|
||||
发布到 npm 可让你的应用在 Twenty 应用市场中被发现。 任何 Twenty 工作区都可以直接通过 UI 浏览、安装和升级应用市场中的应用。
|
||||
|
||||
### Requirements
|
||||
### 要求
|
||||
|
||||
* An [npm](https://www.npmjs.com) account
|
||||
* The `twenty-app` keyword in your `package.json` `keywords` array (add it manually — it is not included by default in the `create-twenty-app` template)
|
||||
* 一个 [npm](https://www.npmjs.com) 账户
|
||||
* 你在 `package.json` 的 `keywords` 数组中的 `twenty-app` 关键字(需要手动添加 — 在 `create-twenty-app` 模板中默认不包含)
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -178,9 +178,9 @@ Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twe
|
||||
}
|
||||
```
|
||||
|
||||
### Marketplace metadata
|
||||
### 应用市场元数据
|
||||
|
||||
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
|
||||
`defineApplication()` 配置支持可选字段,用于控制你的应用在市场中的展示方式。 使用 `logoUrl` 和 `screenshots` 来引用 `public/` 文件夹中的图片:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
@@ -196,33 +196,33 @@ export default defineApplication({
|
||||
});
|
||||
```
|
||||
|
||||
See the [defineApplication accordion](/l/zh/developers/extend/apps/config/application#marketplace-metadata) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
完整的市场字段列表(`author`、`category`、`aboutDescription`、`websiteUrl`、`termsUrl` 等)请参见“构建应用”页面中的 [defineApplication 折叠面板](/l/zh/developers/extend/apps/config/application#marketplace-metadata)。
|
||||
|
||||
#### Recommended screenshot dimensions
|
||||
#### 建议的屏幕截图尺寸
|
||||
|
||||
The marketplace renders `screenshots` in a fixed `8:5` container (for example, `1600×1000 px`).
|
||||
该市场会在固定的 `8:5` 容器中渲染 `screenshots`(例如,`1600×1000 px`)。
|
||||
|
||||
<Note>
|
||||
Screenshots of any aspect ratio are displayed in full and are never cropped, but anything significantly taller or narrower than `8:5` will show empty bands on the sides.
|
||||
任意纵横比的屏幕截图都会完整显示,且绝不会被裁剪,但相对于 `8:5` 明显更高或更窄的图片,两侧会出现空白边。
|
||||
</Note>
|
||||
|
||||
### Publish
|
||||
### 发布
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
To publish under a specific dist-tag (e.g., `beta` or `next`):
|
||||
要在特定的 dist-tag(例如 `beta` 或 `next`)下发布:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### How marketplace discovery works
|
||||
### 应用市场的发现机制如何运作
|
||||
|
||||
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
|
||||
Twenty 服务器会**每小时**从 npm 注册表同步其市场目录。
|
||||
|
||||
You can trigger the sync immediately instead of waiting:
|
||||
你可以立即触发同步,而无需等待:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server catalog-sync
|
||||
@@ -230,15 +230,15 @@ yarn twenty server catalog-sync
|
||||
# yarn twenty server catalog-sync --remote production
|
||||
```
|
||||
|
||||
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
|
||||
市场中显示的元数据来自你的 `defineApplication()` 配置——例如 `displayName`、`description`、`author`、`category`、`logoUrl`、`screenshots`、`aboutDescription`、`websiteUrl` 和 `termsUrl` 等字段。
|
||||
|
||||
<Note>
|
||||
If your app does not define an `aboutDescription` in `defineApplication()`, the marketplace will automatically use your package's `README.md` from npm as the about page content. This means you can maintain a single README for both npm and the Twenty marketplace. If you want a different description in the marketplace, explicitly set `aboutDescription`.
|
||||
如果您的应用未在 `defineApplication()` 中定义 `aboutDescription`,市场将自动使用 npm 上您的软件包的 `README.md` 作为关于页面内容。 这意味着您可以为 npm 和 Twenty 市场维护同一个 README。 如果您希望在市场中使用不同的描述,请显式设置 `aboutDescription`。
|
||||
</Note>
|
||||
|
||||
### CI publishing
|
||||
### CI 发布
|
||||
|
||||
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
使用此 GitHub Actions 工作流,在每次发布时自动进行发布(使用 [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -265,31 +265,31 @@ jobs:
|
||||
working-directory: .twenty/output
|
||||
```
|
||||
|
||||
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty build`, then `npm publish` from `.twenty/output`.
|
||||
对于其他 CI 系统(GitLab CI、CircleCI 等),同样适用以下三条命令:`yarn install`、`yarn twenty build`,然后在 `.twenty/output` 目录下执行 `npm publish`。
|
||||
|
||||
<Note>
|
||||
**npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions.
|
||||
**npm provenance** 可选,但建议启用。 使用 `--provenance` 发布会在你的 npm 列表中添加可信徽章,使用户可以验证该包是由公共 CI 流水线中的特定提交构建的。 有关设置说明,请参见 [npm provenance 文档](https://docs.npmjs.com/generating-provenance-statements)。
|
||||
</Note>
|
||||
|
||||
## Installing apps
|
||||
## 安装应用
|
||||
|
||||
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
|
||||
一旦应用已发布(npm)或已部署(tarball),各工作区就可以通过 UI 进行安装。
|
||||
|
||||
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
|
||||
在 Twenty 的 **Settings > Applications** 页面中,既可以浏览也可以安装来自市场或通过 tarball 部署的应用。
|
||||
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
You can also install apps from the command line:
|
||||
你也可以通过命令行安装应用:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
<Note>
|
||||
The server enforces semver versioning on install, mirroring the rules on deploy:
|
||||
服务器在安装时强制执行 SemVer 版本控制,与部署时的规则一致:
|
||||
|
||||
* Installing the same version that is already installed in your workspace is rejected with an `APP_ALREADY_INSTALLED` error.
|
||||
* Installing a lower version than the one currently installed is rejected with a `CANNOT_DOWNGRADE_APPLICATION` error.
|
||||
* 尝试安装与工作区中已安装版本相同的版本将被拒绝,并返回 `APP_ALREADY_INSTALLED` 错误。
|
||||
* 尝试安装低于当前已安装版本的版本将被拒绝,并返回 `CANNOT_DOWNGRADE_APPLICATION` 错误。
|
||||
|
||||
To install a newer version, deploy or publish it first, then re-run `yarn twenty install`.
|
||||
若要安装较新的版本,请先部署或发布它,然后重新运行 `yarn twenty install`。
|
||||
</Note>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
---
|
||||
title: Testing
|
||||
description: Vitest setup, integration tests against a real Twenty server, type checking, and CI with GitHub Actions.
|
||||
title: 测试
|
||||
description: Vitest 设置、针对真实 Twenty 服务器的集成测试、类型检查,以及使用 GitHub Actions 的 CI。
|
||||
icon: flask
|
||||
---
|
||||
|
||||
The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server.
|
||||
该 SDK 提供可编程的 API,使你可以在测试代码中构建、部署、安装和卸载你的应用。 结合 [Vitest](https://vitest.dev/) 和类型化 API 客户端,你可以编写集成测试,在真实的 Twenty 服务器上验证你的应用端到端运行是否正常。
|
||||
|
||||
## Using npm packages
|
||||
## 使用 npm 包
|
||||
|
||||
You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime.
|
||||
可以在应用中安装并使用任意 npm 包。 逻辑函数和前端组件都通过 [esbuild](https://esbuild.github.io/) 打包,所有依赖都会被内联到输出中——运行时不需要 `node_modules`。
|
||||
|
||||
### Installing a package
|
||||
### 安装包
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add axios
|
||||
```
|
||||
|
||||
Then import it in your code:
|
||||
然后在代码中导入它:
|
||||
|
||||
```ts src/logic-functions/fetch-data.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
@@ -37,7 +37,7 @@ export default defineLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
The same works for front components:
|
||||
前端组件同样适用:
|
||||
|
||||
```tsx src/front-components/chart.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -54,25 +54,25 @@ export default defineFrontComponent({
|
||||
});
|
||||
```
|
||||
|
||||
### How bundling works
|
||||
### 打包的工作原理
|
||||
|
||||
The build step uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle.
|
||||
构建步骤使用 esbuild 为每个逻辑函数和每个前端组件生成一个自包含文件。 所有导入的包都会被内联到打包产物中。
|
||||
|
||||
**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed.
|
||||
**逻辑函数** 运行在 Node.js 环境中。 Node 内置模块(`fs`、`path`、`crypto`、`http` 等) 可用且无需安装。
|
||||
|
||||
**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment.
|
||||
**前端组件** 运行在 Web Worker 中。 Node 内置模块不可用——仅可使用浏览器 API 以及可在浏览器环境中运行的 npm 包。
|
||||
|
||||
Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server.
|
||||
两个环境都将 `twenty-client-sdk/core` 和 `twenty-client-sdk/metadata` 作为预置模块提供 — 这些模块不会被打包,而是在运行时由服务器解析。
|
||||
|
||||
## Setup
|
||||
## 设置
|
||||
|
||||
The scaffolded app already includes Vitest. If you set it up manually, install the dependencies:
|
||||
脚手架生成的应用已包含 Vitest。 如果你手动进行设置,请安装这些依赖:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D vitest vite-tsconfig-paths
|
||||
```
|
||||
|
||||
Create a `vitest.config.ts` at the root of your app:
|
||||
在应用根目录下创建一个 `vitest.config.ts`:
|
||||
|
||||
```ts vitest.config.ts
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
@@ -98,7 +98,7 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
Create a setup file that verifies the server is reachable before tests run:
|
||||
创建一个设置文件,在测试运行前验证服务器可达:
|
||||
|
||||
```ts src/__tests__/setup-test.ts
|
||||
import * as fs from 'fs';
|
||||
@@ -138,22 +138,22 @@ beforeAll(async () => {
|
||||
});
|
||||
```
|
||||
|
||||
## Programmatic SDK APIs
|
||||
## 可编程的 SDK API
|
||||
|
||||
The `twenty-sdk/cli` subpath exports functions you can call directly from test code:
|
||||
子路径 `twenty-sdk/cli` 导出了可直接在测试代码中调用的函数:
|
||||
|
||||
| Function | Description |
|
||||
| -------------- | ------------------------------------------- |
|
||||
| `appBuild` | Build the app and optionally pack a tarball |
|
||||
| `appDeploy` | Upload a tarball to the server |
|
||||
| `appInstall` | Install the app on the active workspace |
|
||||
| `appUninstall` | Uninstall the app from the active workspace |
|
||||
| 函数 | 描述 |
|
||||
| -------------- | ------------------ |
|
||||
| `appBuild` | 构建应用,并可选地打包为 tar 包 |
|
||||
| `appDeploy` | 将 tar 包上传到服务器 |
|
||||
| `appInstall` | 在活动工作区安装该应用 |
|
||||
| `appUninstall` | 从活动工作区卸载该应用 |
|
||||
|
||||
Each function returns a result object with `success: boolean` and either `data` or `error`.
|
||||
每个函数都会返回一个结果对象,包含 `success: boolean`,以及 `data` 或 `error` 之一。
|
||||
|
||||
## Writing an integration test
|
||||
## 编写集成测试
|
||||
|
||||
Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace:
|
||||
下面是一个完整示例:构建、部署并安装该应用,然后验证它出现在工作区中:
|
||||
|
||||
```ts src/__tests__/app-install.integration-test.ts
|
||||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
|
||||
@@ -216,40 +216,40 @@ describe('App installation', () => {
|
||||
});
|
||||
```
|
||||
|
||||
## Running tests
|
||||
## 运行测试
|
||||
|
||||
Make sure your local Twenty server is running, then:
|
||||
确保你的本地 Twenty 服务器正在运行,然后:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn test
|
||||
```
|
||||
|
||||
Or in watch mode during development:
|
||||
或者在开发期间使用监听模式:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn test:watch
|
||||
```
|
||||
|
||||
## Type checking
|
||||
## 类型检查
|
||||
|
||||
You can also run type checking on your app without running tests:
|
||||
你也可以在不运行测试的情况下对应用进行类型检查:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty typecheck
|
||||
```
|
||||
|
||||
This runs `tsc --noEmit` and reports any type errors.
|
||||
这会运行 `tsc --noEmit` 并报告所有类型错误。
|
||||
|
||||
## CI with GitHub Actions
|
||||
## 使用 GitHub Actions 进行 CI
|
||||
|
||||
The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests.
|
||||
脚手架工具会在 `.github/workflows/ci.yml` 生成一个开箱即用的 GitHub Actions 工作流。 它会在每次向 `main` 推送以及拉取请求上自动运行你的集成测试。
|
||||
|
||||
The workflow:
|
||||
工作流:
|
||||
|
||||
1. Checks out your code
|
||||
2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action
|
||||
3. Installs dependencies with `yarn install --immutable`
|
||||
4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs
|
||||
1. 检出你的代码
|
||||
2. 使用 `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` 动作启动一个临时的 Twenty 服务器
|
||||
3. 使用 `yarn install --immutable` 安装依赖
|
||||
4. 运行 `yarn test`,并从该动作的输出中注入 `TWENTY_API_URL` 和 `TWENTY_API_KEY`
|
||||
|
||||
```yaml .github/workflows/ci.yml
|
||||
name: CI
|
||||
@@ -296,6 +296,6 @@ jobs:
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
```
|
||||
|
||||
You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub.
|
||||
你无需配置任何机密——`spawn-twenty-docker-image` 动作会在运行器中直接启动一个临时的 Twenty 服务器,并输出连接详情。 GitHub 会自动提供 `GITHUB_TOKEN` 机密。
|
||||
|
||||
To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow.
|
||||
若要固定为特定的 Twenty 版本而不是 `latest`,请在工作流顶部修改 `TWENTY_VERSION` 环境变量。
|
||||
|
||||
Reference in New Issue
Block a user