snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
---
title: 最佳实践
---
本文档概述了当您在后端工作时应遵循的最佳实践。
## 遵循模块化方法
后端遵循模块化方法,这是使用 NestJS 的基本原则。 确保将代码分解为可重用的模块,以保持代码库的清晰和有序。
每个模块应封装特定的功能或特性,并具有明确的范围。 这种模块化方法使关注点分离更加清晰,并消除了不必要的复杂性。
## 将服务暴露以供模块使用
始终创建具有明确单一职责的服务,这有助于提高代码的可读性和可维护性。 以具有描述性且一致的方式为服务命名。
您还应该公开希望在其他模块中使用的服务。 通过 NestJS 强大的依赖注入系统,可以实现向其他模块公开服务,并促进组件间的松耦合。
## 避免使用 `any` 类型
当您将变量声明为 `any` 类型时,TypeScript 的类型检查器不会进行任何类型检查,使得可以将任何类型的值赋给该变量。 TypeScript 使用类型推断,根据值来确定变量的类型。 若声明为 `any`,TypeScript 将无法再推断其类型。 这使开发过程中难以捕捉到与类型相关的错误,导致运行时错误,使代码维护性更差,可靠性更低,且更难以被他人理解。
因此,一切都应该有一个类型。 因此,如果您创建包含名字和姓氏的新对象,应该创建一个包含名字和姓氏的接口或类型,以定义您正在操作的对象的形状。

View File

@@ -0,0 +1,39 @@
---
title: 自定义对象
---
对象是存储特定于组织的数据(记录、属性和值)的结构。 Twenty 提供标准对象和自定义对象。
标准对象是为所有用户提供特定属性的内置对象。 Twenty 的标准对象示例包括“公司”和“人员”。 标准对象具有对所有 Twenty 用户可用的标准字段,如 Company.displayName。
自定义对象是您可以创建的对象,用于存储专属于您组织的信息。 它们不是内置的;您可以创建和自定义自定义对象来保存标准对象不适合存储的信息。
## 高级模式
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="高级模式" />
</div>
<br />
## 工作原理
自定义对象来自确定对象形状、名称和类型的元数据表。 所有这些信息都存在于元数据模式数据库中,由表组成:
* **DataSource**:说明数据所在的详细信息。
* **Object**:描述对象并链接到数据源。
* **Field**:列出对象的字段并连接到对象。
要添加自定义对象,工作空间成员将查询 /metadata API。 这将相应地更新元数据,并根据元数据计算 GraphQL 架构,将其存储在 GQL 缓存中以备后用。
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="查询 /metadata API 以添加自定义对象" />
</div>
<br />
要获取数据,流程涉及通过 /graphql 端点进行查询并通过查询解析器传递。
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="查询 /graphql 端点以获取数据" />
</div>

View File

@@ -0,0 +1,46 @@
---
title: 功能标志
---
功能标志用于隐藏实验性功能。 对于 Twenty,它们是在工作区级别设置的,而不是在用户级别。
## 添加新的功能标志
在 `FeatureFlagKey.ts` 中添加功能标志:
```ts
type FeatureFlagKey =
| 'IS_FEATURENAME_ENABLED'
| ...;
```
还要将其添加到 `feature-flag.entity.ts` 中的枚举:
```ts
enum FeatureFlagKeys {
IsFeatureNameEnabled = 'IS_FEATURENAME_ENABLED',
...
}
```
要在 **后端** 功能上应用功能标志,请使用:
```ts
@Gate({
featureFlag: 'IS_FEATURENAME_ENABLED',
})
```
要在 **前端** 功能上应用功能标志,请使用:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## 配置部署的功能标志
更改 Table `core.featureFlag` 中的相应记录:
| iD | 键 | workspaceId | 值 |
| -- | ------------------------ | ----------- | --- |
| 随机 | `IS_FEATURENAME_ENABLED` | 工作区 ID | `真` |

View File

@@ -0,0 +1,125 @@
---
title: 文件夹架构
info: 详细了解我们的服务器文件夹架构
---
后端目录结构如下:
```
server
└───ability
└───constants
└───core
└───database
└───decorators
└───filters
└───guards
└───health
└───integrations
└───metadata
└───workspace
└───utils
```
## 能力
定义权限并为每个实体提供处理程序。
## 装饰器
为增强功能在NestJS中定义自定义装饰器。
有关更多详细信息,请参阅[自定义装饰器](https://docs.nestjs.com/custom-decorators)。
## 过滤器
包含异常过滤器,以处理可能在GraphQL端点中出现的异常。
## 守卫
有关更多详细信息,请参见[守卫](https://docs.nestjs.com/guards)。
## 健康
包含一个公开可用的REST API(healthz),返回JSON以确认数据库是否按预期工作。
## 元数据
定义自定义对象并提供GraphQL API(graphql/metadata)。
## 工作区
生成并提供基于元数据的自定义GraphQL架构。
### 工作空间目录结构
```
workspace
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───interfaces
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───interfaces
└───workspace-query-builder
└───factories
└───interfaces
└───workspace-query-runner
└───interfaces
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
```
工作空间目录的根目录包含`workspace.factory.ts`,其中包含`createGraphQLSchema`函数。 此函数通过使用元数据为各个工作空间定制架构来生成工作空间特定的架构。 通过分离架构和解析程序的构建,我们使用`makeExecutableSchema`函数,该函数结合这些离散元素。
这个策略不仅仅是关于组织,它还有助于优化,比如缓存生成的类型定义以提高性能和可扩展性。
### 工作空间架构生成器
生成GraphQL架构,并包括:
#### 工厂:
专门的构造函数用于生成与GraphQL相关的结构。
* type.factory使用`TypeMapperService`将字段元数据翻译为GraphQL类型。
* type-definition.factory创建从`objectMetadata`派生的GraphQL输入或输出对象。
#### GraphQL类型
包括枚举、输入、对象和标量,并作为架构构建的基石。
#### 接口和对象定义
包含GraphQL实体的蓝图,包括诸如`MONEY`或`URL`之类的预定义和自定义类型。
#### 服务
包含负责将FieldMetadataType与其适当GraphQL标量或查询修饰符相关联的服务。
#### 存储
包含`TypeDefinitionsStorage`类,其中包含可重用的类型定义,防止重复GraphQL类型。
### 工作空间解析器生成器
创建用于查询和修改GraphQL架构的解析器函数。
此目录中的每个工厂负责生成不同类型的解析器,如`FindManyResolverFactory`,旨在跨各种表灵活应用。
### 工作空间查询运行器
在数据库上运行生成的查询并解析结果。

View File

@@ -0,0 +1,41 @@
---
title: 消息队列
---
队列支持异步操作的执行。 它们可用于执行后台任务,例如在注册时发送欢迎邮件。
每个用例都将拥有一个继承自 `MessageQueueServiceBase` 的队列类。
当前,我们仅支持使用 `bull-mq`[bull-mq](https://bullmq.io/) 作为队列驱动。
## 创建和使用新队列的步骤
1. 在枚举 `MESSAGE_QUEUES` 下为您的新队列添加队列名称。
2. 提供以队列名称为依赖令牌的队列工厂实现。
3. 在所需模块/服务中以队列名称作为依赖令牌注入您创建的队列。
4. 添加与生产者相同令牌注入的工作类。
### 示例用法
```ts
class Resolver {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
async onSomeAction() {
//business logic
await this.queue.add(someData);
}
}
//async worker
class CustomWorker {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
this.initWorker();
}
async initWorker() {
await this.queue.work(async ({ id, data }) => {
//worker logic
});
}
}
```

View File

@@ -0,0 +1,87 @@
---
title: 后端命令
icon: terminal
---
## 实用命令
这些命令应从 packages/twenty-server 文件夹中执行。
从其他文件夹,您可以运行 `npx nx {command} twenty-server`(或 `npx nx run twenty-server:{command}`)。
### 首次设置
```
npx nx database:reset twenty-server # setup the database with dev seeds
```
### 启动服务器
```
npx nx run twenty-server:start
```
### Lint
```
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### 测试
```
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
注意:如果需要在运行集成测试之前重置数据库,可以运行 `npx nx run twenty-server:test:integration:with-db-reset`。
### 重置数据库
如果您想重置并为数据库预置数据,可以运行以下命令:
```bash
npx nx run twenty-server:database:reset
```
### 迁移
#### 适用于 Core/Metadata 模式中的对象 (TypeORM)
```bash
npx nx run twenty-server:database:migrate:generate
```
## 技术栈
Twenty 主要使用 NestJS 作为后端。
Prisma 是我们最先使用的 ORM。 但为了允许用户创建自定义字段和对象,使用更底层的工具更有意义,因为我们需要进行精细控制。 项目现在使用 TypeORM。
这是当前技术栈的样子。
**核心**
* [NestJS](https://nestjs.com/)
* [TypeORM](https://typeorm.io/)
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
**数据库**
* [Postgres](https://www.postgresql.org/)
**第三方集成**
* [Sentry](https://sentry.io/welcome/) 用于 bug 跟踪
**测试**
* [Jest](https://jestjs.io/)
**工具**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
**开发**
* [AWS EKS](https://aws.amazon.com/eks/)

View File

@@ -0,0 +1,85 @@
---
title: Zapier 应用
---
通过 [Zapier](https://zapier.com/) 无缝同步 Twenty 和 3000 多个应用。 自动化任务,提高生产力,提升客户关系!
## 关于 Zapier
Zapier 是一款工具,可通过连接你的团队每天使用的应用来实现工作流自动化。 Zapier 的基本概念是称为 Zaps 的自动化工作流,包含触发器和操作。
您可以在[这里](https://zapier.com/how-it-works)了解更多关于 Zapier 的工作原理。
## 设置
### 步骤 1: 安装 Zapier 包
```bash
cd packages/twenty-zapier
yarn
```
### 步骤 2: 使用 CLI 登录
使用您的 Zapier 凭证通过 CLI 登录:
```bash
zapier login
```
### 步骤 3: 设置环境变量
从 `packages/twenty-zapier` 文件夹运行:
```bash
cp .env.example .env
```
在本地运行应用程序,访问 [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks),并生成一个 API 密钥。
将 `.env` 文件中的 **YOUR_API_KEY** 值替换为你刚刚生成的 API 密钥。
## 开发
<Warning>
确保在执行任何 `zapier` 命令前运行 `yarn build`。
</Warning>
### 测试
```bash
yarn test
```
### Lint
```bash
yarn format
```
### 编辑代码时监视并编译
```bash
yarn watch
```
### 验证您的 Zapier 应用
```bash
yarn validate
```
### 部署您的 Zapier 应用
```bash
yarn deploy
```
### 列出所有 Zapier CLI 命令
```bash
zapier
```

View File

@@ -0,0 +1,79 @@
---
title: 错误、功能请求与 Pull Request
icon: bug
info: 报告问题、提出功能请求并贡献代码
---
## 报告错误
要报告错误,请[在 GitHub 上创建一个 issue](https://github.com/twentyhq/twenty/issues/new)。
你也可以在[Discord](https://discord.gg/cx5n4Jzs57)上寻求帮助。
## 功能请求
如果你不确定这是否是错误,并且觉得更像是功能请求,那么你可能应该[改为发起一个讨论](https://github.com/twentyhq/twenty/discussions/new)。
## 提交 Pull Request
向 Twenty 贡献代码从一个 Pull Request(PR)开始。
### 开始之前
1. 查看[现有的 issue](https://github.com/twentyhq/twenty/issues)以查找相关工作
2. 对于新功能,请先打开一个 issue 进行讨论
3. 阅读我们的[行为准则](https://github.com/twentyhq/twenty/blob/main/.github/CODE_OF_CONDUCT.md)
### Fork 并克隆
1. 在 GitHub 上 Fork 该仓库
2. 克隆你的 Fork:
```bash
git clone https://github.com/YOUR_USERNAME/twenty.git
cd twenty
```
3. 添加上游远程仓库:
```bash
git remote add upstream https://github.com/twentyhq/twenty.git
```
### 创建分支
```bash
git checkout -b feature/your-feature-name
```
使用具有描述性的分支名称:
* `feature/add-export-button`
* `fix/login-redirect-issue`
* `docs/update-api-guide`
### 进行更改
1. 编写简洁、文档完善的代码
2. 遵循现有的代码风格
3. 为新功能添加测试
4. 如有需要,请更新文档
### 提交你的 PR
1. 推送你的分支:
```bash
git push origin feature/your-feature-name
```
2. 在 GitHub 上发起 PR
3. 填写 PR 模板
4. 关联相关的 issue
### PR 检查清单
* [ ] 代码遵循项目的风格指南
* [ ] 本地测试通过
* [ ] 文档已更新
* [ ] PR 描述清楚说明了更改

View File

@@ -0,0 +1,329 @@
---
title: 最佳实践
icon: star
---
本文档概述了在前端工作时应遵循的最佳实践。
## 状态管理
React 和 Jotai 在代码库中处理状态管理。
### 使用 Jotai 原子来存储状态
根据需要创建足够多的原子来存储你的状态,是一种良好实践。
<Warning>
与其为了通过 props 层层传递而过度精简,不如使用额外的原子。
</Warning>
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### 不要使用 `useRef` 来存储状态
避免使用 `useRef` 来存储状态。
如果想存储状态,应该使用 `useState` 或配合 `useAtomState` 的 Jotai 原子。
如果您觉得需要通过使用 `useRef` 来防止一些再渲染,请了解[如何管理再渲染](#managing-re-renders)。
## 管理再渲染
在 React 中管理再渲染可能很困难。
这里有一些规则以帮助避免不必要的再渲染。
请记住,通过了解其原因,可以**始终**避免再渲染。
### 在根级别工作
通过从根级消除它们,现在在新功能中避免再渲染变得容易。
`PageChangeEffect` sidecar 组件只包含一个 `useEffect` ,它持有页面更改时要执行的所有逻辑。
这样您就知道只有一个地方可以触发再渲染。
### 在代码库中添加 `useEffect` 之前总是考虑一次
重新渲染往往是由不必要的“使用效果”造成的。
您应该想一想您是否需要`useEffect`,或者您是否可以在事件处理函数中移动逻辑。
你会发现移动一个 "handleClick" 或 "handleChange" 函数中的逻辑通常很容易。
您还可以在如 Apollo 的库中找到它们:`onCompleted`,`onError` 等。
### 使用一个兄弟组件提取`useEffect`或数据获取逻辑。
如果你觉得需要在你的根组件中添加 "useEffect" ,你应该考虑在侧边组件中提取它。
对于数据获取逻辑也可以采用相同的方法,使用 Apollo hooks。
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### 使用原子族状态和选择器
原子族状态和选择器是避免重新渲染的好方法。
当您需要存储项目列表时,它们很有用。
### 您不应使用 `React.memo(MyComponent)`
避免使用 `React.memo()` 因为它不能解决再渲染的原因,而是中断了再渲染链,可能导致意外行为并使代码难以重构。
### 限制 `useCallback` 或 `useMemo` 的使用
它们通常没有必要,会使代码难以阅读和维护,而性能提升难以察觉。
## Console.logs
`console.log` 语句在开发过程中非常有价值,提供关于变量值和代码流的实时洞察。 但是,将它们留在生产代码中可能导致几个问题:
1. **性能**:过多的日志记录会影响运行时性能,尤其是在客户端应用程序中。
2. **安全性**:记录敏感数据可能会将重要信息暴露给任何检查浏览器控制台的人。
3. **清洁度**:在控制台中填满日志可能会遮蔽开发人员或工具需要查看的重要警告或错误。
4. **专业性**:检查控制台的最终用户或客户看到数量众多的日志语句,可能会质疑代码的质量和精致度。
确保在提交代码到生产环境前移除所有 `console.logs`。
## 命名
### 变量命名
变量名称应该准确描述变量的用途或功能。
#### 泛化的名称问题
在编程中使用泛化的名称不是理想的,因为缺乏具体性,导致模糊性和降低代码的可读性。 这种名称无法传达变量或函数的目的,使得开发人员在不进行深入调查的情况下难以理解代码的意图。 这样会导致调试时间增加、错误的易发性提高以及在维护和协作中遇到困难。 同时,描述性的命名使代码自解释,更易于导航,提高代码质量和开发人员的生产力。
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### 变量名称中应避免使用的一些词
* 空壳
### 事件处理
事件处理器名称应以 `handle` 开头,而 `on` 是用于命名组件属性事件的前缀。
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## 可选属性
避免为可选属性传递默认值。
**示例**
参见下面定义的 `EmailField` 组件:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**用法**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
## 组件作为属性
尽可能将未实例化的组件作为属性传递,以便子组件可以自行决定需要传递哪些属性。
最常见的例子是图标组件:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
```
为了让 React 理解一个组件就是一个组件,需要使用 PascalCase,然后用 `<MyIcon>` 来实例化它。
## 属性传递:保持简约
在 React 中,属性传递是指通过多个组件层传递状态变量及其设置器,即使中介组件不使用它们。 虽然有时是必要的,过多的属性传递可能会导致:
1. **可读性降低**:在深嵌套组件结构中追踪属性的来源或使用位置可能会变得复杂。
2. **维护挑战**:一个组件的属性结构变化可能需要在多个组件中进行调整,即便它们不直接使用该属性。
3. **组件重用性降低**:一个仅接受众多属性用于向下传递的组件变得不那么通用,且在不同上下文中更难以重用。
如果您觉得使用了过多的属性传递,请参见[状态管理最佳实践](#state-management)。
## 导入
导入时,选择指定的别名而不是指定完整或相对路径。
**句柄别名**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**用法**
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## 模式验证
[Zod](https://github.com/colinhacks/zod) 是用于无类型对象的模式验证器:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## 重大变更
在进行下一步之前,始终进行彻底的手动测试,以确保修改没有在其他地方造成干扰,因为测试尚未全面集成。

View File

@@ -0,0 +1,110 @@
---
title: 文件夹架构
icon: folder-tree
info: 深入了解我们的文件夹架构
---
在本指南中,您将探索项目目录结构的详细信息,以及它如何促进 Twenty 的组织和可维护性。
遵循此文件夹架构约定,可以更轻松地找到与特定功能相关的文件,并确保应用程序的可扩展性和可维护性。
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## 页面
包含由应用程序路由定义的顶级组件。 它们从模块文件夹中导入更多低级组件(详细信息见下文)。
## 模块
每个模块代表一个功能或一组功能,包括其特定组件、状态和操作逻辑。
它们都应遵循以下结构。 您可以在模块中嵌套模块(称为子模块),同样的规则将适用。
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### 上下文
上下文是一种通过组件树传递数据的方法,不必在每个级别手动传 props。
详情请参阅 [React Context](https://react.dev/reference/react#context-hooks)。
### GraphQL
包含片段、查询和变更。
详情请参阅 [GraphQL](https://graphql.org/learn/)。
* 片段
片段是查询的可重用部分,可以在不同地方使用。 使用片段可以更容易地避免代码重复。
详情请参阅 [GraphQL Fragments](https://graphql.org/learn/queries/#fragments)。
* 查询
详情请参阅 [GraphQL Queries](https://graphql.org/learn/queries/)。
* 变更
详情请参阅 [GraphQL Mutations](https://graphql.org/learn/queries/#mutations)。
### 钩子
详情请参阅 [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks)。
### 状态
包含状态管理逻辑。 [Jotai](https://jotai.org) 处理这部分。
* 选择器:派生 Atom(使用 `createAtomSelector`)根据其他 Atom 计算出值,并会自动记忆化。
React 内置状态管理仍然在组件内处理状态。
### 工具
应仅包含可重用的纯函数。 否则,请在 `hooks` 文件夹中创建自定义钩子。
## UI
包含应用程序中使用的所有可重用的 UI 组件。
这个文件夹可以包含子文件夹,像 `data`、`display`、`feedback` 和 `input`,用于特定类型的组件。 每个组件都应是自包含和可重用的,以便您可以在应用程序的不同部分使用它。
通过将 UI 组件与 `modules` 文件夹中的其他组件分开,可以更轻松地保持一致的设计,并在更改 UI 时不影响其他部分(业务逻辑)的代码库。
## 接口和依赖关系
您可以从任何模块中导入其他模块代码,但 `ui` 文件夹除外。 这将使代码易于测试。
### 内部
模块的每个部分(钩子、状态等) 都可以有一个 `internal` 文件夹,其中包含仅在模块内使用的部分。

View File

@@ -0,0 +1,91 @@
---
title: 前端命令
icon: terminal
---
## 实用命令
### 启动应用
```bash
npx nx start twenty-front
```
### 根据API graphql schema重新生成graphql schema
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
或
```bash
npx nx run twenty-front:graphql:generate
```
### Lint
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## 翻译
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### 测试
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## 技术栈
项目有一个干净且简单的技术栈,使用最少的样板代码。
**应用**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL 代码生成](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**测试**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**工具**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## 架构
### 路由
[React Router](https://reactrouter.com/) 负责处理路由。
为了避免不必要的 [re-renders](/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders),所有路由逻辑都在 `PageChangeEffect`中的 `useEffect` 中。
### 状态管理
[Jotai](https://jotai.org/) 处理状态管理。
查看[最佳实践](/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front#state-management)以获取有关状态管理的更多信息。
## 测试
[Jest](https://jestjs.io/) 用于单元测试,而 [Storybook](https://storybook.js.org/) 用于组件测试。
Jest 主要用于测试工具函数,而不是组件本身。
Storybook 用于测试隔离组件的行为,以及展示设计系统。

View File

@@ -0,0 +1,178 @@
---
title: 快捷键
---
## 介绍
当你需要监听一个快捷键时,通常会使用 `onKeyDown` 事件监听器。
然而,在 `twenty-front` 中,你可能会遇到用于不同组件的相同快捷键之间的冲突,这些组件同时挂载。
例如,如果你有一个页面监听回车键,而一个包含选择组件的模态框也监听回车键,在所有这些组件同时挂载时可能会发生冲突。
## `useScopedHotkeys` hook
为了解决这个问题,我们有一个自定义的 hook,可以在不产生冲突的情况下监听快捷键。
您可以将它放在组件中,它只会在组件挂载并且指定的 **快捷键范围** 激活时监听快捷键。
## 在实践中如何监听快捷键?
设置快捷键监听涉及两个步骤:
1. 设置将会监听快捷键的[快捷键范围](#what-is-a-hotkey-scope-)
2. 使用 `useScopedHotkeys` hook 来监听快捷键
即使在简单的页面中,也需要设置快捷键范围,因为其他界面元素如左侧菜单或命令菜单也可能监听快捷键。
## 快捷键的使用案例
通常情况下,会有两种需要快捷键的使用案例:
1. 在页面或页面中挂载的组件中
2. 在由于用户操作而获得焦点的模态组件中
第二种使用情况可以递归发生:例如,在模态框中的下拉框。
### 在页面中监听快捷键
示例:
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### 在模态类型组件中监听快捷键
在这个示例中,我们将使用一个模态组件,该组件监听 Esc 键来告诉其父组件关闭自己。
这里用户交互正在改变范围。
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
然后在模态组件中:
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
当你不能确定只需使用使用效果挂载/卸载就足以避免冲突时,使用此模式是很重要的。
这些冲突可能难以调和,而且使用效果可能更经常地发生。
## 什么是快捷键范围?
快捷键范围是表示快捷键激活上下文的字符串。 通常编码为枚举。
当你更改快捷键范围时,监听此范围的快捷键将启用,监听其他范围的快捷键将被禁用。
一次只能设置一个范围。
例如,每个页面的快捷键范围在 `PageHotkeyScope` 枚举中定义:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
在内部,当前选择的范围存储在整个应用程序共享的 Jotai 原子中:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
但这个原子不应该手动处理! 我们将在下一节中学习如何使用它。
## 内部是如何运作的?
我们在 [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) 之上制作了一个薄包装,使其性能更高并避免不必要的重新渲染。
我们还创建了一个 Jotai 原子来处理快捷键范围状态,并使其在整个应用程序中可用。

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: 浏览 Twenty 的 UI 组件库
---
在 Storybook 中查看我们完整的组件库和文档。
[打开 Storybook →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: 样式指南
icon: paintbrush
---
本文档包含编写代码时需要遵循的规则。
这里的目标是拥有一个一致的代码库,易于阅读和维护。
为此,比起过于简洁,冗长一些更好。
请始终记住,人们阅读代码的次数远多于他们编写代码的次数,特别是在开源项目中,任何人都可以贡献。
有很多规则没有在此定义,但可以通过linters自动检查。
## React
### 使用函数组件
始终使用 TSX 函数组件。
不要使用带有`const`的默认`import`,因为它更难阅读,并且更难通过代码补全功能导入。
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### 属性
创建props类型并称为`(ComponentName)Props`,如果不需要导出它。
使用props解构。
```tsx
// ❌ Bad, no type
export const MyComponent = (props) => <div>Hello {props.name}</div>;
// ✅ Good, type
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### 避免使用 `React.FC` 或 `React.FunctionComponent` 适用于属性类型的定义
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
* - With `React.FC`, the component implicitly accepts a `children` prop
* even if it's not defined in the prop type. This might not always be
* desirable, especially if the component doesn't intend to render
* children.
*/
const EmailField: React.FC<{
value: string;
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
```
#### JSX元素中禁止单变量属性传播
避免在JSX元素中使用单个变量属性传播,如`{...props}`。 这种做法通常会导致代码不易读且难以维护,因为不清楚组件接收了哪些属性。
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
*/
const MyComponent = (props: OwnProps) => {
return <OtherComponent {...props} />;
}
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
理由:
* 一目了然,它更清楚哪个属性代码传递下来,这使得理解和维护更容易。
* 这有助于防止组件间通过属性的紧密耦合。
* Linting工具使显式列出属性时更易于识别拼写错误或未使用的属性。
## JavaScript
### 使用nullish 合并运算符 `??`
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### 使用可选链 `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### 使用 `type` 替代 `interface`
总是使用 `type` 而不是 `interface`,因为它们几乎总是重叠,而 `type` 更灵活。
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### 使用字符串字面量代替枚举
[字符串字面量](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types)是TypeScript中处理枚举值的首选方式。 它们可以更轻松地使用Pick和Omit扩展,并提供更好的开发者体验,特别是在代码补全时。
您可以查看[这里](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums)了解为什么TypeScript建议避免使用枚举。
```tsx
// ❌ Bad, utilizes an enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
let color = Color.Red;
```
```tsx
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL和内部库
应使用GraphQL codegen生成的枚举。
在使用内部库时, 使用枚举更好,这样内部库不必暴露不属于内部API的字符串文字类型。
示例:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## 样式
### 使用StyledComponents
使用[Linaria styled](https://github.com/callstack/linaria)对组件进行样式化。
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
在“真实”组件和Styled 组件之间的区别,前置“Styled”命名。
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### 主题
大多数组件样式首选利用主题。
#### 测量单位
避免在样式化组件中直接使用`px`或`rem`值。 所需的值通常已在主题中定义,因此建议为此目的使用主题。
#### 颜色
避免引入新颜色;而是使用来自主题的现有调色板。 如果遇到调色板不匹配的情况,请留下评论以便团队纠正。
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
font-weight: 400;
margin-left: 4px;
border-radius: 50px;
`;
```
```tsx
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(1)};
border-radius: ${({ theme }) => theme.border.rounded};
`;
```
## 强制不允许类型导入
避免类型导入。 为强制实施此标准,Oxlint 规则会检查并报告所有类型导入。 这有助于在TypeScript代码中保持一致性和可读性。
```tsx
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
### 为什么不允许类型导入
* **一致性**:通过避免类型导入并为类型和值导入使用单一方法,代码库在其模块导入风格上保持一致性。
* **可读性**:无类型导入提高代码可读性,使得在导入值或类型时更显而易见。 这减少了歧义,并使理解导入符号的用途更容易。
* **可维护性**:它增强了代码库的可维护性,因为开发人员可以在审查或修改代码时识别和定位仅类型导入。
### Oxlint 规则
Oxlint 规则,`typescript/consistent-type-imports`,强制执行不允许类型导入标准。 该规则会生成错误或警告以提示任何类型导入的违规。
请注意,此规则专门针对在罕见的边缘情况中发生的非预期类型导入。 TypeScript 本身也不鼓励这种做法,如[TypeScript 3.8 发行说明](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html)所述。 在大多数情况下,您不需要使用仅类型导入。
为确保您的代码符合此规则,请确保在开发工作流程中运行 Oxlint。

View File

@@ -0,0 +1,61 @@
---
title: 使用Figma进行工作
info: 了解如何与 Twenty 的 Figma 协作
---
Figma是一款协作界面设计工具,帮助设计师和开发者之间沟通无障碍。
本指南介绍如何与Figma协作。
## 访问
1. **访问共享链接:** 您可以在[此处](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty)访问项目的 Figma 文件。
2. **登录:** 如果尚未登录,Figma会提示您进行登录。
只有登录用户才能使用关键功能,如开发者模式和选择专用框架的能力。
<Warning>
没有账户将无法有效协作。
</Warning>
## Figma结构
在左侧边栏,您可以访问 Twenty 的 Figma 中的不同页面。 页面组织方式如下:
* **组件页面:** 这是第一页。 设计师使用它创建和组织设计文件中可重用的设计元素。 例如,按钮、图标、符号,或其他可重用的组件。 它能保持设计一致性。
* **主页:** 第二页是主页,展示项目的完整用户界面。 可以按下**播放**,使用完整的应用程序原型。
* **功能页面:** 其他页面通常专用于进行中的功能。 它们包含应用程序或网站特定功能或模块的设计。 这些通常仍在进行中。
## 有用的提示
在仅查看权限下,您无法编辑设计,但可以访问用于将设计转换为代码的所有功能。
### 使用开发者模式
Figma的开发者模式通过提供便捷的设计导航、有效的资产管理、有效的沟通工具、工具箱集成、快速代码片段和关键图层信息,增强了开发人员的生产力,填补了设计与开发之间的缺口。 可以在[此处](https://www.figma.com/dev-mode/)了解有关开发者模式的更多信息。
切换到工具栏右侧的“开发者”模式以查看设计规格、复制CSS并访问资产。
### 使用原型
在画布上点击任意元素,并在界面右上角按下“播放”按钮以访问原型视图。 原型模式允许您像最终产品一样与设计进行交互。 它展示了屏幕之间的流程以及界面元素(如按钮、链接或菜单)在交互时的行为方式。
1. **了解过渡和动画:** 在原型模式中,您可以查看设计师在屏幕或UI元素之间添加的任何过渡或动画,为开发人员提供明确的视觉说明,理解预期的行为和风格。
2. **实施澄清:** 原型还可以帮助减少歧义。 开发人员可以与原型交互,以更好地理解特定元素的功能或外观。
有关Figma平台学习的更多详细信息和指南,可以访问官方[Figma文档](https://help.figma.com/hc/en-us)。
### 测量距离
选择一个元素,按住`Option`键(Mac)或`Alt`键(Windows),然后悬停在另一个元素上以查看它们之间的距离。
### VSCode的Figma扩展(推荐)
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
可让您浏览和检查设计文件,与设计师协作,跟踪更改,并加速实现落地——且无需离开文本编辑器。
这是我们推荐的扩展之一。
## 协作
1. **使用评论:** 您可以使用评论功能,点击工具栏左侧的气泡图标。
2. **光标聊天:** Figma的一个很棒的功能是光标聊天。 如果您看到其他人在使用Figma的同时按下Mac上的`;`或者Windows上的`/`来发送消息。

View File

@@ -0,0 +1,317 @@
---
title: 本地设置
icon: laptop-code
description: 本指南适用于希望在本地运行 Twenty 的贡献者或好奇的开发人员。
---
## 先决条件
<Tabs>
<Tab title="Linux 和 macOS">
在安装和使用 Twenty 之前,请确保在您的计算机上安装以下内容:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
* [yarn v4](https://yarnpkg.com/getting-started/install)
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
<Warning>
`npm` 无法使用,您应该改用 `yarn`。 Yarn 现在与 Node.js 一起提供,因此您不需要单独安装它。
如果您尚未执行此操作,只需运行 `corepack enable` 即可启用 Yarn。
</Warning>
</Tab>
<Tab title="Windows (WSL)">
1. 安装 WSL
以管理员身份打开 PowerShell 并运行:
```powershell
wsl --install
```
现在您应该看到一个提示,要求您重启计算机。 如果没有,请手动重启。
重启后,将打开一个 PowerShell 窗口并安装 Ubuntu。 这可能需要一些时间。
您将看到一个提示,要求为您的 Ubuntu 安装创建用户名和密码。
2. 安装和配置 git
```bash
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
```
3. 安装 nvm、node.js 和 yarn
<Warning>
使用 `nvm` 安装正确的 `node` 版本。 `.nvmrc` 确保所有贡献者使用相同的版本。
</Warning>
```bash
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
关闭并重新打开您的终端以使用 nvm。 然后运行以下命令。
```bash
nvm install # installs recommended node version
nvm use # use recommended node version
corepack enable
```
</Tab>
</Tabs>
---
## 步骤 1:Git 克隆
在您的终端中,运行以下命令。
<Tabs>
<Tab title="SSH (推荐)">
如果您尚未设置 SSH 密钥,您可以在 [这里](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh) 学习如何设置。
```bash
git clone git@github.com:twentyhq/twenty.git
```
</Tab>
<Tab title="HTTPS">
```bash
git clone https://github.com/twentyhq/twenty.git
```
</Tab>
</Tabs>
## 步骤 2:定位到项目根目录
```bash
cd twenty
```
您应该从项目的根目录运行以下步骤中的所有命令。
## 步骤 3:设置 PostgreSQL 数据库
<Tabs>
<Tab title="Linux">
\*\*选项 1(首选):\*\*要在本地预配您的数据库:
使用以下链接在 Linux 机器上安装 PostgreSQL:[PostgreSQL 安装](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
注意:您可能需要在命令 `psql` 前添加 `sudo -u postgres` 以避免权限错误。
\*\*选项 2:\*\*如果您已安装 docker:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Mac OS">
\*\*选项 1(推荐):\*\*通过 `brew` 在本地设置数据库:
```bash
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
您可以通过执行以下操作来验证 PostgreSQL 服务器是否正在运行:
```bash
brew services list
```
在 macOS 上通过 Homebrew 安装时
安装程序可能不会默认创建 `postgres` 用户。 相反,它会创建一个与您的 macOS 用户名(例如,“john”)匹配的 PostgreSQL 角色。
按照以下步骤检查并在必要时创建 `postgres` 用户:
```bash
# Connect to PostgreSQL
psql postgres
or
psql -U $(whoami) -d postgres
```
在 psql 提示符(postgres=#)下,运行:
```bash
# List existing PostgreSQL roles
\du
```
您将看到类似于以下的输出:
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
如果您没有看到 `postgres` 角色列出,请继续下一步。
手动创建 `postgres` 角色:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
这将创建一个具备登录权限名为 `postgres` 的超级用户角色。
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
\*\*选项 2:\*\*如果您已安装 docker:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
以下所有步骤应在 WSL 终端(在您的虚拟机内)中运行
\*\*选项 1:\*\*要在本地预配您的 PostgreSQL:
使用以下链接在 Linux 虚拟机上安装 PostgreSQL:[PostgreSQL 安装](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
注意:您可能需要在命令 `psql` 前添加 `sudo -u postgres` 以避免权限错误。
\*\*选项 2:\*\*如果您已安装 docker:
在 WSL 上运行 Docker 增加了额外的复杂性。
仅在您对涉及的额外步骤感到舒适时使用此选项,包括开启 [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl)。
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
</Tabs>
您现在可以通过 `localhost:5432` 访问数据库。
如果您使用了上面的 Docker 选项,默认凭据为用户 `postgres`、密码 `postgres`。 对于原生 PostgreSQL 安装,请使用在您的机器上已配置的凭据和角色。
## 步骤 4:设置 Redis 数据库(缓存)
Twenty 需要 Redis 缓存来提供最佳性能。
<Tabs>
<Tab title="Linux">
\*\*选项 1:\*\*在本地供应您的 Redis:
使用以下链接在 Linux 机器上安装 Redis:[Redis 安装](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
\*\*选项 2:\*\*如果您已安装 docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Mac OS">
\*\*选项 1(推荐):\*\*通过 `brew` 在本地设置 Redis:
```bash
brew install redis
```
启动您的 Redis 服务器:
```bash
brew services start redis
```
\*\*选项 2:\*\*如果您已安装 docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
\*\*选项 1:\*\*在本地供应您的 Redis:
使用以下链接在 Linux 虚拟机上安装 Redis:[Redis 安装](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
\*\*选项 2:\*\*如果您已安装 docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
</Tabs>
如果您需要客户端 GUI,我们推荐 [Redis Insight](https://redis.io/insight/)(提供免费版本)。
## 步骤 5:设置环境变量
使用环境变量或 `.env` 文件配置您的项目。 更多信息请参见 [此处](/l/zh/developers/self-host/capabilities/setup).
复制 `/front` 和 `/server` 目录中的 `.env.example` 文件:
```bash
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**多工作区模式:** 默认情况下,Twenty 以单工作区模式运行,在此模式下只能创建一个工作区。 要启用多工作区支持(对于测试基于子域的功能很有用),请在服务器的 `.env` 文件中设置 `IS_MULTIWORKSPACE_ENABLED=true`。 详见 [多工作区模式](/l/zh/developers/self-host/capabilities/setup#multi-workspace-mode)。
</Info>
## 步骤 6:安装依赖项
要构建 Twenty 服务器并向你的数据库预置一些数据,请运行以下命令:
```bash
yarn
```
注意 `npm` 或 `pnpm` 将不起作用
## 步骤 7:运行项目
<Tabs>
<Tab title="Linux">
根据您的 Linux 发行版,Redis 服务器可能会自动启动。
如果没有,请查看您的发行版的 [Redis 安装指南](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/)。
</Tab>
<Tab title="Mac OS">
Redis 应该已经在运行中。 如果没有,运行:
```bash
brew services start redis
```
</Tab>
<Tab title="Windows (WSL)">
根据您的 Linux 发行版,Redis 服务器可能会自动启动。
如果没有,请查看您的发行版的 [Redis 安装指南](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/)。
</Tab>
</Tabs>
使用以下命令设置您的数据库:
```bash
npx nx database:reset twenty-server
```
启动服务器、工作程序和前端服务:
```bash
npx nx start twenty-server
npx nx worker twenty-server
npx nx start twenty-front
```
或者,您可以一次启动所有服务:
```bash
npx nx start
```
## 步骤 8:使用 Twenty
**前端**
Twenty 的前端将在 [http://localhost:3001](http://localhost:3001) 运行。
您可以使用默认的演示帐号登录:`tim@apple.dev` (密码:`tim@apple.dev`)
**后端**
* Twenty 的服务器将在 [http://localhost:3000](http://localhost:3000) 启动并运行
* 可在 [http://localhost:3000/graphql](http://localhost:3000/graphql) 访问 GraphQL API
* 可通过 [http://localhost:3000/rest](http://localhost:3000/rest) 访问 REST API
## 故障排除
如果您遇到任何问题,请检查[故障排除](/l/zh/developers/self-host/capabilities/troubleshooting)以获取解决方案。

View File

@@ -0,0 +1,77 @@
---
title: 命令
icon: terminal
description: 用于开发 Twenty 的实用命令。
---
可以在存储库根目录使用 `npx nx` 运行命令。 使用 `npx nx run {project}:{command}` 显式指定目标。
## 启动应用
```bash
npx nx start twenty-front # Frontend dev server (http://localhost:3001)
npx nx start twenty-server # Backend server (http://localhost:3000)
npx nx run twenty-server:worker # Background worker
```
## 数据库
```bash
npx nx database:reset twenty-server # Reset and seed database
npx nx run twenty-server:database:migrate:prod # Run migrations
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow> # Generate a migration
```
## 代码检查
```bash
npx nx lint:diff-with-main twenty-front # Lint changed files (fastest)
npx nx lint:diff-with-main twenty-server
npx nx lint twenty-front --configuration=fix # Auto-fix
```
## 类型检查
```bash
npx nx typecheck twenty-front
npx nx typecheck twenty-server
```
## 测试
```bash
# Frontend
npx nx test twenty-front # Jest unit tests
npx nx storybook:build twenty-front # Build Storybook
npx nx storybook:test twenty-front # Storybook tests
# Backend
npx nx run twenty-server:test:unit # Unit tests
npx nx run twenty-server:test:integration # Integration tests
npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset
# Single file (fastest)
npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs
```
## GraphQL
```bash
npx nx run twenty-front:graphql:generate # Regenerate types
npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema
```
## 翻译
```bash
npx nx run twenty-front:lingui:extract # Extract strings
npx nx run twenty-front:lingui:compile # Compile translations
```
## 构建
```bash
npx nx build twenty-shared # Must be built first
npx nx build twenty-front
npx nx build twenty-server
```

View File

@@ -0,0 +1,31 @@
---
title: 贡献
description: 为 Twenty 的开源开发做出贡献。
---
<Frame>
<img src="/images/user-guide/github/github-header.png" alt="AI" />
</Frame>
## 概览
Twenty 是开源的,欢迎社区贡献。 无论您是在修复错误、添加功能,还是改进文档,您的贡献都将帮助 Twenty 变得更好,惠及所有人。
## 贡献方式
* **报告错误**: 帮助识别并记录问题
* **提交功能**: 提出并实现新功能
* **改进文档**: 让我们的文档更清晰、更有帮助
* **前端开发**: 参与基于 React 的 UI 开发
* **后端开发**: 为 NestJS 服务器做出贡献
## 开始使用
<CardGroup cols={2}>
<Card title="错误报告与功能请求" icon="bug" href="/l/zh/developers/contribute/capabilities/bug-and-requests">
报告问题或请求功能
</Card>
<Card title="前端开发" icon="browser" href="/l/zh/developers/contri/capabilities/frontend-development/folder-structure-front">
为 UI 做出贡献
</Card>
</CardGroup>

View File

@@ -0,0 +1,176 @@
---
title: 样式指南
icon: paintbrush
description: 为 Twenty 做出贡献的代码约定和最佳实践。
---
## React
### 仅使用函数组件
始终使用带命名导出的 TSX 函数组件。
```tsx
// ❌ Bad
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good
export function MyComponent() {
return <div>Hello World</div>;
};
```
### 属性
创建一个名为 `{ComponentName}Props` 的类型。 使用解构。 不要使用 `React.FC`。
```tsx
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
### 禁止仅为单个 prop 使用展开
```tsx
// ❌ Bad
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
// ✅ Good
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
```
## 状态管理
### 使用 Jotai 原子管理全局状态
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
```
* 优先使用原子,而非 props 逐层传递
* 不要将 `useRef` 用于状态 — 使用 `useState` 或原子。
* 对列表使用原子族和选择器。
### 避免不必要的重新渲染
* 将 `useEffect` 和数据获取提取到同级的 sidecar 组件中。
* 优先使用事件处理器(`handleClick`、`handleChange`)而不是 `useEffect`。
* 不要使用 `React.memo()` — 请改为修复根本原因。
* 限制 `useCallback` / `useMemo` 的使用。
```tsx
// ❌ Bad — useEffect in the same component causes re-renders
export const Page = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <div>{data}</div>;
};
// ✅ Good — extract into sibling
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <></>;
};
export const Page = () => {
const [data] = useAtomState(dataState);
return <div>{data}</div>;
};
```
## TypeScript
* **优先用 `type` 而非 `interface`** — 更灵活、更易组合。
* **优先用字符串字面量而非枚举** — 但 GraphQL 代码生成的枚举和内部库 API 除外。
* **禁止 `any`** — 强制启用严格的 TypeScript。
* **不使用类型导入** — 使用常规导入(由 Oxlint `typescript/consistent-type-imports` 强制执行)。
* **使用 [Zod](https://github.com/colinhacks/zod)** 对无类型对象进行运行时校验。
## JavaScript
```tsx
// Use nullish-coalescing (??) instead of ||
const value = process.env.MY_VALUE ?? 'default';
// Use optional chaining
onClick?.();
```
## 命名
* **变量**:使用 camelCase,具描述性(`email` 而非 `value`,`fieldMetadata` 而非 `fm`)。
* **常量**:SCREAMING_SNAKE_CASE
* **类型/类**:PascalCase
* **文件/目录**:kebab-case(`.component.tsx`、`.service.ts`、`.entity.ts`)
* **事件处理器**:`handleClick`(处理函数不要使用 `onClick` 作为名称)
* **组件 props**:以组件名作为前缀(`ButtonProps`)。
* **样式化组件**:以 `Styled` 作为前缀(`StyledTitle`)。
## 样式
使用 [Linaria](https://github.com/callstack/linaria) 的样式化组件。 使用主题值 — 避免硬编码 `px`、`rem` 或颜色。
```tsx
// ❌ Bad
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
margin-left: 4px;
`;
// ✅ Good
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
margin-left: ${({ theme }) => theme.spacing(1)};
`;
```
## 导入
使用别名而不是相对路径:
```tsx
// ❌ Bad
import { Foo } from '../../../../../testing/decorators/Foo';
// ✅ Good
import { Foo } from '~/testing/decorators/Foo';
import { Bar } from '@/modules/bar/components/Bar';
```
## 文件夹架构
```
front
└── modules/ # Feature modules
│ └── module1/
│ ├── components/
│ ├── constants/
│ ├── contexts/
│ ├── graphql/ (fragments, queries, mutations)
│ ├── hooks/
│ ├── states/ (atoms, selectors)
│ ├── types/
│ └── utils/
└── pages/ # Route-level components
└── ui/ # Reusable UI components (display, input, feedback, ...)
```
* 模块可以相互导入,但 `ui/` 应保持无依赖
* 将模块私有代码放在 `internal/` 子文件夹中
* 组件不超过 300 行,服务不超过 500 行。