i18n - docs translations (#22715)

Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22715?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
github-actions[bot]
2026-07-09 11:51:54 +02:00
committed by GitHub
parent a0cf4cc9e1
commit ebee7d71b9
228 changed files with 4216 additions and 4583 deletions
@@ -4,7 +4,7 @@ description: Execute lógica antes ou depois da instalação — para popular da
icon: wrench
---
Hooks de instalação são funções de lógica especiais que são executadas durante o ciclo de vida de instalação ou atualização. Elas compartilham o mesmo runtime de handler que as [logic functions](/l/pt/developers/extend/apps/logic/logic-functions) normais e recebem um `InstallPayload`, mas são declaradas com suas próprias funções de definição — `definePostInstallLogicFunction()` e `definePreInstallLogicFunction()` — e ficam fora do modelo de gatilhos normal (HTTP, cron, eventos de banco de dados).
Hooks de instalação são funções de lógica especiais que são executadas durante o ciclo de vida de instalação ou atualização. Elas compartilham o mesmo runtime de handler que as [logic functions](/l/pt/developers/extend/apps/logic/logic-functions) normais e recebem um `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` é `undefined` em uma instalação nova), mas são declaradas com suas próprias funções de definição e ficam fora do modelo de gatilhos normal (HTTP, cron, eventos de banco de dados).
Cada aplicativo pode definir no máximo uma função de pré-instalação e no máximo uma função de pós-instalação. A geração do manifesto apresentará erro se mais de uma de cada for detectada.
@@ -19,111 +19,59 @@ Cada aplicativo pode definir no máximo uma função de pré-instalação e no m
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="É executada depois que a migração de metadados do workspace é aplicada">
## Visão geral
Uma função de pós-instalação é executada automaticamente assim que seu aplicativo termina de ser instalado em um workspace. O servidor a executa **depois** que os metadados do aplicativo forem sincronizados e o cliente do SDK for gerado, para que o espaço de trabalho esteja totalmente pronto para uso e o novo esquema esteja disponível. Casos de uso típicos incluem popular dados padrão, criar registros iniciais, configurar as definições do espaço de trabalho ou provisionar recursos em serviços de terceiros.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Execuções | Antes da migração de metadados — o esquema e os dados **anteriores** ainda estão intactos | Após a migração e a geração do SDK — o **novo** esquema está em vigor |
| Execução | Sempre síncrona; bloqueia a instalação | Assíncrona por padrão (em fila, 3 novas tentativas); modo síncrono por opt-in via `shouldRunSynchronously: true` |
| Em caso de falha | A instalação é **abortada** antes de qualquer alteração de esquema | Assíncrono: novas tentativas até 3 vezes. Síncrono: o chamador recebe `POST_INSTALL_ERROR` (as alterações de esquema **não** são revertidas) |
| Uso típico | Fazer backup ou corrigir dados que uma migração poderia perder; recusar uma atualização arriscada lançando uma exceção | Popular dados padrão, configurar o workspace, registrar recursos externos |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**Regra geral:** use post-install como padrão. Recurra à pré-instalação somente quando a própria migração for destrutiva e você precisar interceptar o estado anterior antes que ele desapareça.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| Você quer... | Usar |
| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Popular dados, configurar o workspace, registrar recursos externos | `post-install` |
| Trabalho de longa duração que não deve bloquear a resposta da instalação | `post-install` (modo assíncrono padrão, com novas tentativas do worker) |
| Configuração rápida da qual o chamador depende imediatamente após o retorno da instalação | `post-install` com `shouldRunSynchronously: true` |
| Ler ou fazer backup de dados que a próxima migração perderia | `pre-install` |
| Rejeitar uma atualização que corromperia dados existentes | `pre-install` (lançar uma exceção no manipulador) |
| Reconciliação em cada atualização | Qualquer um dos hooks com `shouldRunOnVersionUpgrade: true` |
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
## Comportamento compartilhado por ambos os hooks
Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI:
* A configuração é uma config de `defineLogicFunction` menos as configurações de gatilho, mais `shouldRunOnVersionUpgrade`.
* **Quando é executado**: apenas em instalações novas, por padrão. Defina `shouldRunOnVersionUpgrade: true` para também executar em atualizações. Use `previousVersion` / `newVersion` para ramificar com base no caminho de atualização.
* **Idempotência é importante**: o post-install assíncrono pode ser executado novamente, e qualquer um dos hooks é reexecutado em atualizações quando `shouldRunOnVersionUpgrade` está ativado.
* O ambiente usual de logic-function (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) é injetado, para que você possa chamar a Twenty API com o token do seu app.
* O hook é anexado automaticamente ao manifesto da aplicação em tempo de build (`preInstallLogicFunction` / `postInstallLogicFunction`) — nada para referenciar em [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
* O `timeoutSeconds` padrão é 300 para permitir tarefas de configuração mais longas, como o pré-carregamento de dados.
* **Não é executado em modo de desenvolvimento**: `yarn twenty dev` ignora o fluxo de instalação e sincroniza os arquivos diretamente, portanto os hooks nunca são executados ali. Em vez disso, acione-os manualmente:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
Pontos-chave:
* As funções de pós-instalação usam `definePostInstallLogicFunction()` — uma variante especializada que omite as configurações de gatilho (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* O manipulador recebe um `InstallPayload` com `{ previousVersion?: string; newVersion: string }` — `newVersion` é a versão que está sendo instalada, e `previousVersion` é a versão que foi instalada anteriormente (ou `undefined` em uma instalação nova). Use esses valores para distinguir instalações novas de atualizações e para executar lógica de migração específica da versão.
* **Quando o hook é executado**: apenas em instalações novas, por padrão. Passe `shouldRunOnVersionUpgrade: true` se você também quiser que ele seja executado quando o app for atualizado a partir de uma versão anterior. Quando omitida, a flag tem valor padrão `false` e as atualizações ignoram o hook.
* **Modelo de execução — assíncrono por padrão, síncrono opcional**: a flag `shouldRunSynchronously` controla *como* a pós-instalação é executada.
* `shouldRunSynchronously: false` *(padrão)* — o hook é **enfileirado na fila de mensagens** com `retryLimit: 3` e é executado de forma assíncrona em um worker. A resposta da instalação retorna assim que o job é enfileirado, então um manipulador lento ou com falha não bloqueia quem chamou. O worker tentará novamente até três vezes. **Use isto para jobs de longa duração** — popular grandes conjuntos de dados, chamar APIs de terceiros lentas, provisionar recursos externos, qualquer coisa que possa exceder uma janela razoável de resposta HTTP.
* `shouldRunSynchronously: true` — o hook é executado **inline durante o fluxo de instalação** (mesmo executor da pré-instalação). A requisição de instalação bloqueia até o manipulador terminar e, se ele lançar uma exceção, quem chamou a instalação recebe um `POST_INSTALL_ERROR`. Sem novas tentativas automáticas. **Use isto para trabalhos rápidos que precisam ser concluídos antes da resposta** — por exemplo, emitir um erro de validação para o usuário ou fazer uma configuração rápida da qual o cliente dependerá imediatamente após a chamada de instalação retornar. Tenha em mente que a migração de metadados já foi aplicada quando a pós-instalação é executada, então uma falha no modo síncrono **não** reverte as alterações de esquema — ela apenas expõe o erro.
* Garanta que seu manipulador seja idempotente. No modo assíncrono, a fila pode tentar novamente até três vezes; em qualquer modo, o hook pode ser executado novamente em atualizações quando `shouldRunOnVersionUpgrade: true`.
* As variáveis de ambiente `APPLICATION_ID`, `APP_ACCESS_TOKEN` e `API_URL` estão disponíveis dentro do manipulador (assim como em qualquer outra função de lógica), então você pode chamar a API da Twenty com um token de acesso de aplicativo com escopo para o seu app.
* É permitida apenas uma função de pós-instalação por app. A geração do manifesto apresentará erro se mais de uma for detectada.
* O `universalIdentifier`, `shouldRunOnVersionUpgrade` e `shouldRunSynchronously` da função são anexados automaticamente ao manifesto do aplicativo no campo `postInstallLogicFunction` durante o build — você não precisa referenciá-los em [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados.
* **Não executado no modo de desenvolvimento**: quando um app é registrado localmente (via `yarn twenty dev`), o servidor pula completamente o fluxo de instalação e sincroniza arquivos diretamente pelo watcher da CLI — portanto, a pós-instalação nunca é executada no modo de desenvolvimento, independentemente de `shouldRunSynchronously`. Use `yarn twenty dev:function:exec --postInstall` para acioná-lo manualmente em um workspace em execução.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="É executada antes que a migração de metadados do workspace seja aplicada">
Uma função de pré-instalação é executada automaticamente durante a instalação, **antes que a migração de metadados do workspace seja aplicada**. Ela compartilha o mesmo formato de payload que a pós-instalação (`InstallPayload`), mas está posicionada mais cedo no fluxo de instalação para poder preparar o estado do qual a próxima migração depende — usos típicos incluem fazer backup de dados, validar a compatibilidade com o novo esquema ou arquivar registros que estão prestes a ser reestruturados ou removidos.
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
Você também pode executar manualmente a função de pré-instalação a qualquer momento usando a CLI:
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
Pontos-chave:
* Funções de pré-instalação usam `definePreInstallLogicFunction()` — a mesma configuração especializada da pós-instalação, apenas anexada a um ponto diferente do ciclo de vida.
* Os manipuladores de pré e pós-instalação recebem o mesmo tipo `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importe-o uma vez e reutilize-o para ambos os hooks.
* **Quando o hook é executado**: posicionado imediatamente antes da migração de metadados do workspace (`synchronizeFromManifest`). Antes de executar, o servidor realiza uma "sincronização simplificada" puramente aditiva que registra a função de pré-instalação da **nova** versão nos metadados do workspace — nada mais é alterado — e então a executa. Como essa sincronização é apenas aditiva, os objetos, campos e dados da versão anterior ainda estão intactos quando seu manipulador é executado: você pode ler e fazer backup com segurança do estado pré-migração.
* **Modelo de execução**: a pré-instalação é executada **de forma síncrona** e **bloqueia a instalação**. Se o manipulador lançar uma exceção, a instalação é abortada antes que quaisquer alterações de esquema sejam aplicadas — o workspace permanece na versão anterior em um estado consistente. Isto é intencional: a pré-instalação é sua última chance de recusar uma atualização arriscada.
* Assim como na pós-instalação, é permitida apenas uma função de pré-instalação por app. Ela é anexada ao manifesto do aplicativo sob `preInstallLogicFunction` automaticamente durante o build.
* **Não é executada no modo de desenvolvimento**: igual à pós-instalação — o fluxo de instalação é totalmente ignorado para apps registrados localmente, portanto a pré-instalação nunca é executada com `yarn twenty dev`. Use `yarn twenty dev:function:exec --preInstall` para acioná-lo manualmente.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="É executada depois que a migração de metadados do workspace é aplicada">
</Accordion>
<Accordion title="Pré-instalação vs pós-instalação: quando usar cada um" description="Escolhendo o hook de instalação correto">
Ambos os hooks fazem parte do mesmo fluxo de instalação e recebem o mesmo `InstallPayload`. A diferença é **quando** eles são executados em relação à migração de metadados do workspace, e isso muda quais dados eles podem manipular com segurança.
A pré-instalação é sempre **síncrona** (ela bloqueia a instalação e pode abortá-la). A pós-instalação é **assíncrona por padrão** — enfileirada em um worker com novas tentativas automáticas — mas pode optar por execução síncrona com `shouldRunSynchronously: true`. Veja o acordeão `definePostInstallLogicFunction` acima para saber quando usar cada modo.
**Use `post-install` para qualquer coisa que precise que o novo esquema exista.** Este é o caso mais comum:
* Popular dados padrão (criando registros iniciais, visualizações padrão, conteúdo de demonstração) em objetos e campos recém-adicionados.
* Registrar webhooks com serviços de terceiros agora que o app tem suas credenciais.
* Chamar sua própria API para finalizar a configuração que depende dos metadados sincronizados.
* Lógica idempotente de "garantir que isso exista" que deve reconciliar o estado em cada atualização — combine com `shouldRunOnVersionUpgrade: true`.
Exemplo — popular um registro `PostCard` padrão após a instalação:
É executado depois que seu app termina de ser instalado: metadados sincronizados, cliente SDK gerado, novo esquema disponível para consulta. Exemplo — popular um registro padrão em instalações novas:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = createClient();
await client.postCard.create({
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
@@ -133,22 +81,28 @@ export default definePostInstallLogicFunction({
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
**Use `pre-install` quando uma migração, de outra forma, destruiria ou corromperia dados existentes.** Como a pré-instalação roda contra o esquema *anterior* e sua falha reverte a atualização, é o lugar certo para qualquer coisa arriscada:
A flag `shouldRunSynchronously` controla o modelo de execução:
* **Fazer backup de dados que estão prestes a ser removidos ou reestruturados** — por exemplo, você está removendo um campo na v2 e precisa copiar seus valores para outro campo ou exportá-los para um armazenamento antes que a migração seja executada.
* **Arquivar registros que uma nova restrição invalidaria** — por exemplo, um campo está se tornando `NOT NULL` e você precisa excluir ou corrigir linhas com valores nulos primeiro.
* **Validar a compatibilidade e recusar a atualização se os dados atuais não puderem ser migrados de forma limpa** — lance uma exceção no manipulador e a instalação é abortada sem alterações aplicadas. Isto é mais seguro do que descobrir a incompatibilidade no meio da migração.
* **Renomear ou reatribuir chaves de dados** antes de uma alteração de esquema que perderia a associação.
* `false` *(padrão)* — colocado em fila na message queue (`retryLimit: 3`) e executado por um worker. A resposta da instalação retorna assim que o job é colocado na fila. **Use para trabalhos de longa duração** — popular grandes conjuntos de dados, APIs lentas de terceiros.
* `true` — executado inline durante o fluxo de instalação. A requisição de instalação fica bloqueada até que o handler termine; um erro lançado aparece como `POST_INSTALL_ERROR` para o chamador (sem novas tentativas). **Use para trabalhos rápidos que precisam ser concluídos antes da resposta.** A migração já foi aplicada neste ponto, portanto uma falha não reverte as alterações de esquema — ela apenas expõe o erro.
Exemplo — arquivar registros antes de uma migração destrutiva:
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="É executada antes que a migração de metadados do workspace seja aplicada">
É executado antes da migração de metadados, contra o esquema **anterior** — o lugar certo para fazer backup de dados que uma migração poderia perder ou para recusar uma atualização arriscada. Antes de executar, o servidor realiza uma "sincronização simplificada" puramente aditiva que registra apenas a função de pré-instalação da nova versão; todo o resto — objetos, campos e dados da versão anterior — permanece intocado quando seu handler é executado.
A pré-instalação é sempre **síncrona** e bloqueia a instalação. Se o handler lançar uma exceção, a instalação é abortada antes de qualquer alteração de esquema — o workspace permanece na versão anterior em um estado consistente. Isto é intencional: a pré-instalação é sua última chance de recusar uma atualização arriscada.
Exemplo — copiar os valores de um campo legado antes que a migração o remova:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { createClient } from './generated/client';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
@@ -156,24 +110,24 @@ const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise
return;
}
const client = createClient();
const legacyRecords = await client.postCard.findMany({
where: { notes: { isNotNull: true } },
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
if (legacyRecords.length === 0) return;
// Copy legacy `notes` into the new `description` field before the migration
// drops the `notes` column. If this fails, the upgrade is aborted and the
// workspace stays on v1 with all data intact.
await Promise.all(
legacyRecords.map((record) =>
client.postCard.update({
where: { id: record.id },
data: { description: record.notes },
}),
),
);
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
@@ -186,21 +140,5 @@ export default definePreInstallLogicFunction({
});
```
**Regra geral:**
| Você quer... | Usar |
| ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Popular dados padrão, configurar o workspace, registrar recursos externos | `post-install` |
| Executar processos longos de popular dados ou chamadas a terceiros que não devem bloquear a resposta da instalação | `post-install` (padrão — `shouldRunSynchronously: false`, com novas tentativas do worker) |
| Executar uma configuração rápida da qual o chamador dependerá imediatamente após o retorno da chamada de instalação | `post-install` com `shouldRunSynchronously: true` |
| Ler ou fazer backup de dados que a próxima migração perderia | `pre-install` |
| Rejeitar uma atualização que corromperia dados existentes | `pre-install` (lançar uma exceção no manipulador) |
| Executar reconciliação em cada atualização | `post-install` com `shouldRunOnVersionUpgrade: true` |
| Fazer uma configuração única apenas na primeira instalação | `post-install` com `shouldRunOnVersionUpgrade: false` (padrão) |
<Note>
Em caso de dúvida, use **post-install** como padrão. Recurra à pré-instalação somente quando a própria migração for destrutiva e você precisar interceptar o estado anterior antes que ele desapareça.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**Os campos base são adicionados automaticamente.** Quando você define um objeto personalizado, o Twenty cria campos padrão como `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt` para você. Você não precisa declará‑los no seu array `fields` — apenas seus campos personalizados. Você pode substituir um campo padrão declarando um com o mesmo nome, mas isso raramente é uma boa ideia.
</Note>
## Tipos de campo
O conjunto completo de valores de `FieldType`, exportados de `twenty-sdk/define`:
| Categoria | Tipos |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Texto | `TEXT`, `RICH_TEXT`, `ARRAY` (de strings), `RAW_JSON` |
| Numérico | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (precisão arbitrária), `RATING`, `POSITION` |
| Datas | `DATE`, `DATE_TIME` |
| Escolha | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Composto | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identificadores e relações | `UUID`, `RELATION`, `MORPH_RELATION` (veja [Relações](/l/pt/developers/extend/apps/data/relations)) |
| Sistema | `TS_VECTOR` (vetor de pesquisa de texto completo, gerenciado pelo servidor) |
Tipos compostos armazenam vários subcampos (por exemplo, `FULL_NAME` = primeiro + último nome; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` e `MULTI_SELECT` exigem um array `options`, como no exemplo acima.
## Valores padrão
Valores padrão de strings literais devem ser colocados entre aspas simples **dentro** da string — `defaultValue: "'Draft'"`, não `defaultValue: "Draft"`. É por isso que o campo `status` acima usa `` `'${PostCardStatus.DRAFT}'` ``.
@@ -14,26 +14,39 @@ my-twenty-app/
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
setup-test.ts
app-install.integration-test.ts
.github/workflows/ci.yml # GitHub Actions
public/ # Static assets
vitest.config.ts # Test runner config
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, LLMS.md
README.md, AGENTS.md, CLAUDE.md
```
## Arquivos principais
| Arquivo / Pasta | Finalidade |
| ---------------------------------------- | ------------------------------------------------------------------------ |
| `src/application-config.ts` | **Obrigatório.** O principal arquivo de configuração do seu aplicativo. |
| `src/default-role.ts` | Papel padrão que controla o que suas funções de lógica podem acessar. |
| `src/constants/universal-identifiers.ts` | UUIDs gerados automaticamente e metadados (nome de exibição, descrição). |
| `src/__tests__/` | Testes de integração (configuração + teste de exemplo). |
| `public/` | Recursos estáticos (imagens, fontes) servidos com seu aplicativo. |
| Arquivo / Pasta | Finalidade |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Obrigatório.** O principal arquivo de configuração do seu aplicativo. |
| `src/default-role.ts` | Papel padrão que controla o que suas funções de lógica podem acessar. |
| `src/constants/universal-identifiers.ts` | UUIDs gerados automaticamente e metadados (nome de exibição, descrição). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Uma página de boas-vindas inicial: um front component renderizado por um page layout autônomo, acessível a partir da barra lateral. |
| `src/__tests__/` | Um teste de unidade mais um teste de integração (com sua configuração global) que sincroniza o app com um servidor real. |
| `public/` | Recursos estáticos (imagens, fontes) servidos com seu aplicativo. |
| `AGENTS.md` / `CLAUDE.md` | Orientação para agentes de codificação de IA que trabalham no app. |
<Note>
**A organização de arquivos fica a seu critério.** As pastas acima são convenções — o SDK detecta entidades por meio de análise de AST em chamadas a `export default defineEntity(...)`, independentemente de onde o arquivo esteja.
@@ -47,15 +60,18 @@ Ambos os pacotes Twenty SDK pertencem a `devDependencies`, não a `dependencies`
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "^2.13.0",
"twenty-sdk": "^2.13.0"
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
O scaffolder fixa `twenty-sdk` e `twenty-client-sdk` para a sua própria versão — mantenha os dois sincronizados ao atualizar.
* **`twenty-sdk`** inclui a CLI `twenty` e as ferramentas de build/scaffolding. Ele é executado apenas durante o desenvolvimento e o build e nunca é importado pelo runtime do aplicativo publicado.
* **`twenty-client-sdk`** *é* importado pelo código do seu aplicativo (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), mas a Twenty o fornece em tempo de execução — as funções de lógica o obtêm de uma camada SDK gerada, e os componentes de front o resolvem a partir de módulos servidos pelo servidor. A cópia instalada é usada apenas para verificação de tipos e para o build no momento do deploy, então ela nunca precisa ser incluída no bundle implantado.
Manter qualquer um dos pacotes em `dependencies` o inclui no bundle de runtime do aplicativo instalado, onde ele é peso morto. `twenty build` emite um aviso quando qualquer um deles ainda está listado em `dependencies`.
Manter qualquer um dos pacotes em `dependencies` o inclui no bundle de runtime do aplicativo instalado, onde ele é peso morto. `twenty dev:build` emite um aviso quando qualquer um deles ainda está listado em `dependencies`.
Adicione as dependências de runtime do próprio aplicativo (bibliotecas que as suas funções de lógica realmente importam em tempo de execução) em `dependencies`, como de costume.
@@ -6,17 +6,17 @@ description: Crie seu primeiro app do Twenty em minutos.
## Pré-requisitos
* **Node.js 24+** — [Baixar](https://nodejs.org/)
* **Node.js 24.5+** — [Baixar](https://nodejs.org/)
* **Yarn 4** — Vem com o Node.js via Corepack. Ative-o: `corepack enable`
* **Docker** — [Baixar](https://www.docker.com/products/docker-desktop/). Necessário para executar um servidor Twenty local. Ignore se você já tiver o Twenty em execução em outro lugar.
A criação de um aplicativo Twenty tem três fases. A ferramenta de scaffolding as reúne em um único comando do fluxo ideal, mas cada fase é um conceito separado — quando algo falha, saber em que fase você está indica o que corrigir.
| Fase | O que você faz | Ferramenta | Resultado |
| --------------------------- | -------------------------------------------------- | ----------------------------- | ------------------------------------- |
| **1. Criar scaffolding** | Gerar o código-fonte do aplicativo | `npx create-twenty-app` | Um projeto TypeScript em disco |
| **2. Executar um servidor** | Iniciar um servidor Twenty para o qual sincronizar | Docker + `yarn twenty server` | Uma instância Twenty em execução |
| **3. Sincronizar** | Sincronize seu código em tempo real com o servidor | `yarn twenty dev` | Suas alterações aparecem na interface |
| Fase | O que você faz | Ferramenta | Resultado |
| --------------------------- | -------------------------------------------------- | ----------------------------------- | ------------------------------------- |
| **1. Criar scaffolding** | Gerar o código-fonte do aplicativo | `npx create-twenty-app` | Um projeto TypeScript em disco |
| **2. Executar um servidor** | Iniciar um servidor Twenty para o qual sincronizar | Docker + `yarn twenty docker:start` | Uma instância Twenty em execução |
| **3. Sincronizar** | Sincronize seu código em tempo real com o servidor | `yarn twenty dev` | Suas alterações aparecem na interface |
---
@@ -28,7 +28,7 @@ Crie um novo aplicativo a partir do modelo:
npx create-twenty-app@latest my-twenty-app
```
Você será solicitado a informar um nome e uma descrição — pressione **Enter** para aceitar os valores padrão. Isso gera um projeto TypeScript em `my-twenty-app/` com um `application-config.ts` inicial, um papel padrão, um fluxo de trabalho de CI e um teste de integração.
O gerador não é interativo: o nome do diretório se torna o nome do app. Passe `--display-name` e `--description` para personalizar os metadados gerados (você também pode editá-los depois em `src/constants/universal-identifiers.ts`). Isso gera um projeto TypeScript em `my-twenty-app/` com um `application-config.ts` inicial, um papel padrão, fluxos de trabalho de CI/CD e um teste de integração.
**Após esta fase:** você tem o código-fonte de um aplicativo na sua máquina. Ele ainda não está em execução — isso é a Fase 2.
@@ -38,28 +38,14 @@ Você será solicitado a informar um nome e uma descrição — pressione **Ente
Seu aplicativo precisa de um servidor Twenty para o qual sincronizar. O servidor é uma instância completa do Twenty — interface, API GraphQL, PostgreSQL — executando localmente no Docker. Seu código local envia suas definições para esse servidor, o que faz com que elas apareçam na interface.
A ferramenta de scaffolding oferece iniciar um para você:
O scaffolder inicia uma instância para você: com o Docker em execução, ele baixa a imagem `twentycrm/twenty-app-dev`, inicia-a na porta `2020` e autentica a CLI no workspace de demonstração pré-preenchido (`tim@apple.dev`) — sem necessidade de login.
> **Você gostaria de configurar uma instância local do Twenty?**
* **Sim (recomendado)** — baixa a imagem Docker `twentycrm/twenty-app-dev` e a inicia na porta `2020`. Certifique-se de que o Docker esteja em execução primeiro.
* **Não** — escolha isto se você já tiver um servidor Twenty ao qual deseja se conectar. Você pode conectá-lo depois com `yarn twenty remote:add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Deve iniciar instância local?" />
</div>
Quando o servidor estiver ativo, um navegador será aberto para login. Use a conta de demonstração pré-configurada:
* **E-mail:** `tim@apple.dev`
* **Senha:** `tim@apple.dev`
Para se conectar a um servidor Twenty existente em vez disso, passe `--url \<your-server-url>`. Servidores remotos se autenticam com OAuth: um navegador é aberto para que você faça login e clique em **Authorize**, o que dá à CLI acesso ao seu workspace. (Você também pode optar por usar OAuth localmente com `--authentication-method oauth` — faça login com `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Tela de login do Twenty" />
</div>
Clique em **Authorize** na próxima tela — isso dá à CLI acesso ao seu espaço de trabalho.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Tela de autorização da CLI do Twenty" />
</div>
@@ -117,27 +103,31 @@ Clique em **View installed app** para ver a instalação no espaço de trabalho.
### Sincronização única para CI e scripts
Passe `--once` para executar uma única compilação + sincronização e sair — mesmo pipeline, sem watcher:
Use `plan` e `apply` para executar o mesmo pipeline uma vez, sem watcher:
```bash filename="Terminal"
yarn twenty dev --once
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Comando | Comportamento | Quando usar |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `yarn twenty dev` | Monitora e ressincroniza a cada alteração. Fica em execução até você interrompê-lo. | Desenvolvimento local interativo. |
| `yarn twenty dev --once` | Executa uma única compilação + sincronização e, em seguida, encerra com o código `0` em caso de sucesso ou `1` em caso de falha. | Scripts, CI, hooks de pre-commit, agentes de IA e fluxos de trabalho com script. |
| `yarn twenty dev --once --dry-run` | Compila e imprime as alterações de metadados **sem aplicá-las**. | Inspecionar o que uma sincronização mudaria antes de confirmá-la. |
| Comando | Comportamento | Quando usar |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `yarn twenty dev` | Monitora e ressincroniza a cada alteração. Fica em execução até você interrompê-lo. | Desenvolvimento local interativo. |
| `yarn twenty apply` | Executa uma única compilação + sincronização e, em seguida, encerra com o código `0` em caso de sucesso ou `1` em caso de falha. Pede confirmação para alterações destrutivas (passe `--force` para pular). | Scripts, CI, hooks de pre-commit, agentes de IA e fluxos de trabalho com script. |
| `yarn twenty plan` | Compila e imprime as alterações de metadados **sem aplicá-las**. | Inspecionar o que uma sincronização mudaria antes de confirmá-la. |
Ambos os modos precisam de um remoto autenticado. Veja [Sincronização e recuperação](/l/pt/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run) para mais detalhes sobre `--dry-run`.
Todos os modos precisam de um remoto autenticado. Veja [Sincronização e recuperação](/l/pt/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) para mais detalhes sobre `plan`.
<Note>
`yarn twenty dev --once` e `yarn twenty dev --once --dry-run` são aliases obsoletos para `yarn twenty apply` e `yarn twenty plan`.
</Note>
### Opções do modo de desenvolvimento
| Opção | Descrição |
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `--once` | Compila e sincroniza uma vez e, em seguida, sai. |
| `--dry-run` | Com `--once`, visualize as alterações de metadados sem aplicá‑las. Não grava nada. |
| `--debounceMs \<ms>` | Define o atraso de debounce para alterações de arquivo em milissegundos (padrão: `2000`). |
| `--force` | Aplicar alterações destrutivas (exclusões) sem confirmação. |
| `--debounceMs \<ms>` | Define o atraso de debounce para alterações de arquivo em milissegundos (padrão: `1000`). |
| `--verbose` / `--debug` | Mostra registros detalhados de compilação, solicitações de sincronização e rastreamentos de erro. |
## O que você pode criar
@@ -34,6 +34,10 @@ yarn twenty dev:add frontComponent
| Vista | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Item do menu de navegação | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Layout da página | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Aba Layout da Página | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Item do menu de comandos | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Campo da Vista | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Provedor de conexão | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## O que o scaffolder gera
@@ -5,10 +5,10 @@ icon: wrench
---
* **Erros do Docker** — Certifique-se de que o Docker Desktop (ou o daemon) esteja em execução antes de `yarn twenty docker:start`. A mensagem de erro mostrará o comando de inicialização correto para o seu sistema operacional.
* **Versão errada do Node** — É necessário 24 ou superior. Verifique com `node -v`.
* **Versão errada do Node** — É necessário 24.5+ (`engines.node: ^24.5.0`). Verifique com `node -v`.
* **Falta o Yarn 4** — Execute `corepack enable`.
* **Dependências com problemas** — `rm -rf node_modules && yarn install`.
* **Erros do `twenty-sdk` após a atualização para a v2.8.0** — Ele foi movido de `dependencies` para `devDependencies` na v2.8.0. Veja [Estrutura do projeto → Dependências](/l/pt/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty build` emite um aviso sobre `twenty-client-sdk` em `dependencies`** — Ele é fornecido em tempo de execução pela Twenty, então deve ser movido para `devDependencies` junto com `twenty-sdk`. Veja [Estrutura do projeto → Dependências](/l/pt/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` emite um aviso sobre `twenty-client-sdk` em `dependencies`** — Ele é fornecido em tempo de execução pela Twenty, então deve ser movido para `devDependencies` junto com `twenty-sdk`. Veja [Estrutura do projeto → Dependências](/l/pt/developers/extend/apps/getting-started/project-structure#dependencies).
Travou? Peça ajuda no [Discord da Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
@@ -13,7 +13,6 @@ export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
icon: 'IconLayoutDashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
@@ -22,51 +21,23 @@ export default defineCommandMenuItem({
## Campos de configuração
| Campo | Obrigatório | Descrição |
| --------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Sim | ID exclusivo e estável para o comando |
| `label` | Sim | Rótulo completo exibido no menu de comandos (Cmd+K) |
| `frontComponentUniversalIdentifier` | Sim | O `universalIdentifier` do componente de front-end que este comando abre |
| `shortLabel` | Não | Rótulo mais curto exibido no botão fixado de ação rápida |
| `icon` | Não | Nome do ícone exibido ao lado do rótulo (por exemplo, `'IconBolt'`, `'IconSend'`) |
| `isPinned` | Não | Quando `true`, mostra o comando como um botão de ação rápida no canto superior direito da página |
| `availabilityType` | Não | Controla onde o comando aparece: `'GLOBAL'` (sempre disponível), `'RECORD_SELECTION'` (apenas quando registros estão selecionados) ou `'FALLBACK'` (exibido quando nenhum outro comando corresponde) |
| `availabilityObjectUniversalIdentifier` | Não | Restringe o comando a páginas de um tipo específico de objeto (por exemplo, somente em registros de Company) |
| `conditionalAvailabilityExpression` | Não | Uma expressão booleana que controla dinamicamente a visibilidade (veja abaixo) |
| Campo | Obrigatório | Descrição |
| --------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Sim | ID exclusivo e estável para o comando |
| `label` | Sim | Rótulo completo exibido no menu de comandos (Cmd+K) |
| `frontComponentUniversalIdentifier` | Sim | O `universalIdentifier` do componente de front-end que este comando abre |
| `shortLabel` | Não | Rótulo mais curto exibido no botão fixado de ação rápida |
| `icon` | Não | **Obsoleto** — ignorado em favor do ícone da aplicação; a compilação emite um aviso se definido |
| `isPinned` | Não | Quando `true`, mostra o comando como um botão de ação rápida no canto superior direito da página |
| `availabilityType` | Não | Controla onde o comando aparece: `'GLOBAL'` (sempre disponível), `'GLOBAL_OBJECT_CONTEXT'` (apenas em páginas com um contexto de objeto — páginas de índice e de registro), `'RECORD_SELECTION'` (apenas quando registros estão selecionados) ou `'FALLBACK'` (exibido quando nenhum outro comando corresponde) |
| `availabilityObjectUniversalIdentifier` | Não | Restringe o comando a páginas de um tipo específico de objeto (por exemplo, somente em registros de Company) |
| `conditionalAvailabilityExpression` | Não | Uma expressão booleana que controla dinamicamente a visibilidade (veja abaixo) |
## Comandos sem interface
Um item do menu de comandos emparelhado com um [componente de front-end sem interface](/l/pt/developers/extend/apps/layout/front-components#headless-vs-non-headless) é a forma idiomática de disponibilizar uma ação de um clique — executar código, navegar ou confirmar e executar. A página de Front Components aborda os [SDK Command components](/l/pt/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) que lidam com o padrão de ação e desmontagem.
Um fluxo típico:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
const RunAction = () => {
const execute = async () => {
const client = new CoreApiClient();
await client.mutation({
createTask: {
__args: { data: { title: 'Created by my app' } },
id: true,
},
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
Um fluxo típico: um componente headless renderiza `<Command execute={...} />` (veja o [exemplo completo](/l/pt/developers/extend/apps/layout/front-components#sdk-command-components)), e o item de menu de comando aponta para ele:
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
@@ -74,7 +45,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -49,14 +49,13 @@ export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
icon: 'IconBolt',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
Após sincronizar com `yarn twenty dev` (ou executando uma única vez o `yarn twenty dev --once`), a ação rápida aparece no canto superior direito da página:
Após sincronizar com `yarn twenty dev` (ou executando uma única vez o `yarn twenty apply`), a ação rápida aparece no canto superior direito da página:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Botão de ação rápida no canto superior direito" />
@@ -88,11 +87,11 @@ Os componentes de front-end têm dois modos de renderização controlados pela o
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
@@ -116,7 +115,7 @@ Como o componente retorna `null`, o Twenty ignora renderizar um contêiner para
O pacote `twenty-sdk` fornece quatro componentes auxiliares Command projetados para componentes de front-end headless. Cada componente executa uma ação ao montar, trata erros exibindo uma notificação de snackbar e desmonta automaticamente o componente de front-end ao concluir.
Importe-os de `twenty-sdk/command`:
Importe-os de `twenty-sdk/front-component`:
* **`Command`** — Executa um callback assíncrono via a prop `execute`.
* **`CommandLink`** — Navega para um caminho do app. Props: `to`, `params`, `queryParams`, `options`.
@@ -127,8 +126,8 @@ Aqui está um exemplo completo de um componente de front-end headless usando `Co
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { CoreApiClient } from 'twenty-sdk/clients';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
@@ -160,7 +159,6 @@ import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
icon: 'IconPlayerPlay',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
@@ -169,7 +167,7 @@ E um exemplo usando `CommandModal` para solicitar confirmação antes de executa
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/command';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
@@ -202,7 +200,7 @@ Os componentes de front são executados no navegador em um Web Worker isolado, e
Uma função lógica declarada com `httpRouteTriggerSettings` é acessível por HTTP em seu caminho de rota. Twenty injeta no worker a URL base a partir da qual suas funções são servidas como `TWENTY_FUNCTIONS_URL`, juntamente com o `TWENTY_APP_ACCESS_TOKEN` que autentica a chamada. Ainda não há um cliente SDK dedicado para invocar suas próprias funções, portanto chame-as com um simples `fetch`:
> **No Twenty Cloud, funções lógicas acionadas por HTTP são servidas em um domínio dedicado por workspace** em `https://\<your-workspace-subdomain>.twenty.com\<path>` — que é exatamente para onde `TWENTY_FUNCTIONS_URL` aponta. Para chamadores externos, copie a URL exata das configurações de **HTTP trigger** da função ou da guia **Settings** do aplicativo.
> **No Twenty Cloud, funções lógicas acionadas por HTTP são servidas em um domínio dedicado por workspace** em `https://\<your-workspace-subdomain>.withtwenty.com\<path>` — que é exatamente para onde `TWENTY_FUNCTIONS_URL` aponta. Para chamadores externos, copie a URL exata das configurações de **HTTP trigger** da função ou da guia **Settings** do aplicativo.
<Warning>
A rota legada da função `/s/` está **obsoleta** e será **desativada em 2026-07-24**. Use `TWENTY_FUNCTIONS_URL` (acima) em vez disso e migre quaisquer URLs de `/s/` fixas no código antes dessa data. A rota `/s/` continua disponível para auto-hospedagem.
@@ -212,7 +210,7 @@ Um componente de front headless pode executar a chamada ao montar via o componen
```tsx src/front-components/sync-prs.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/command';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
@@ -316,13 +314,13 @@ Dentro do seu componente, use hooks do SDK para acessar o usuário atual, o regi
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useRecordId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
@@ -405,12 +403,11 @@ Aqui está um exemplo que usa a API do host para exibir um snackbar e fechar o p
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useRecordId } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const recordId = useRecordId();
const [recordId] = useSelectedRecordIds();
const handleArchive = async () => {
const client = new CoreApiClient();
@@ -451,10 +448,10 @@ export default defineFrontComponent({
Use `useSelectedRecordIds()` para lidar com vários registros selecionados. Isso é útil para operações em lote:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent, numberOfSelectedRecords } from 'twenty-sdk/define';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-sdk/clients';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
@@ -492,12 +489,19 @@ export default defineFrontComponent({
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
command: {
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
conditionalAvailabilityExpression: numberOfSelectedRecords > 0,
},
});
```
Exiba-o com um [item de menu de comando](/l/pt/developers/extend/apps/layout/command-menu-items) restrito a seleções de registros:
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
@@ -35,6 +35,8 @@ export default defineNavigationMenuItem({
* `position` controla a ordenação na barra lateral.
* O enum também contém `NavigationMenuItemType.RECORD`, usado internamente para favoritos de registros criados pelo usuário — não pode ser usado a partir de um manifesto de app (não há nenhum campo para fazer referência a um registro).
* `icon` e `color` são opcionais e personalizam a aparência da entrada.
* `folderUniversalIdentifier` também está disponível em qualquer item para aninhá-lo dentro de um pai do tipo `FOLDER`.
@@ -33,17 +33,32 @@ export default defineView({
## Pontos-chave
* `objectUniversalIdentifier` especifica a qual objeto esta visualização se aplica. Pode ser um objeto personalizado que você definiu ou um objeto padrão do Twenty.
* `key` determina o tipo de visualização — `ViewKey.INDEX` é a visualização de lista principal do objeto.
* `key: ViewKey.INDEX` marca a visualização como a visualização principal de lista do objeto (aquela que um item de navegação `OBJECT` abre).
* `fields` controla quais colunas aparecem e em que ordem. Cada campo referencia um `fieldMetadataUniversalIdentifier`.
* Você também pode declarar `filters`, `filterGroups`, `groups` e `fieldGroups` para configurações avançadas.
* Você também pode declarar `filters`, `filterGroups`, `sorts`, `groups` e `fieldGroups` para configurações avançadas.
* `position` controla a ordenação quando existem várias visualizações para o mesmo objeto.
## Propriedades opcionais
| Propriedade | Valores | Descrição |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | `ViewType.TABLE` (padrão), `ViewType.KANBAN`, `ViewType.CALENDAR` | Como os registros são dispostos. (`FIELDS_WIDGET` / `TABLE_WIDGET` também existem, mas são usados internamente por widgets de layout de página.) |
| `visibility` | `ViewVisibility.WORKSPACE` (padrão), `ViewVisibility.UNLISTED` | Se a visualização é listada para todo o workspace ou ocultada dos seletores. |
| `openRecordIn` | `ViewOpenRecordIn.SIDE_PANEL` (padrão), `ViewOpenRecordIn.RECORD_PAGE` | Onde clicar em um registro o abre. |
| `ordenações` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Ordem de classificação padrão. |
| `isCompact` | `boolean` | Exibição compacta de linhas. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Agrupar registros (por exemplo, colunas kanban) por um campo. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Agregações e dimensionamento de colunas kanban. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Visualizações de calendário: layout e o campo de data que posiciona os registros. |
Todos os enums acima são exportados de `twenty-sdk/define`.
## Filtros
Uma visualização pode vir com filtros pré-aplicados. Cada filtro tem três coordenadas: o **campo** a ser filtrado, o **operador** (como comparar) e o **valor** (com o que comparar). As três precisam estar alinhadas — usar um operador que não se aplica a um tipo de campo será rejeitado no momento da sincronização.
```ts
import { ViewFilterOperand } from 'twenty-shared/types';
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
@@ -51,8 +51,12 @@ export default defineLogicFunction({
```
Tipos de gatilho disponíveis:
* **httpRoute**: Expõe sua função em um caminho e método HTTP **no endpoint `/s/`**:
> por exemplo, `path: '/post-card/create'` é acessível em `https://your-twenty-server.com/s/post-card/create`
* **httpRoute**: expõe sua função em um caminho HTTP e método na **URL base do seu espaço de trabalho** — o valor de 20 injeções como `TWENTY_FUNCTIONS_URL` (em Vinte nuvens, um domínio dedicado por espaço de trabalho):
> por exemplo, `path: '/post-card/create'` é acessível em `https://your-workspace.withtwenty.com/post-card/create`
<Warning>
A rota de prefixo `/s/` do legado (`https://your-twenty-server.com/s/post-card/create`) está **obsoleta em 20 Cloud** e será desativada em **2026-07-24**. Persiste disponível para instâncias auto-hospedadas e locais que não configuram um domínio de funções isoladas — use `TWENTY_FUNCTIONS_URL` quando estiver definido, e cair de volta para `\<server-url>/s/\<path>` caso contrário.
</Warning>
<Note>
Para invocar uma função de lógica acionada por rota a partir de um componente de front-end (headless), consulte [Chamando uma função de lógica](/l/pt/developers/extend/apps/layout/front-components#calling-a-logic-function).
@@ -40,13 +40,13 @@ A **camada de lógica** de um app do Twenty é o código que *é executado* —
Uma função de lógica escolhe um ou mais gatilhos — cada entrada abaixo é um campo separado em `defineLogicFunction()`:
| Disparador | Quando é executado | Configuração |
| ----------------------------- | ----------------------------------------------------------------- | ------------------------------- |
| **Rota HTTP** | Uma solicitação atinge seu endpoint `/s/\<path>` | `httpRouteTriggerSettings` |
| **Cron** | Uma expressão CRON corresponde | `cronTriggerSettings` |
| **Evento de banco de dados** | Um registro do workspace é criado, atualizado ou excluído | `databaseEventTriggerSettings` |
| **Ferramenta de IA** | Um recurso de IA do Twenty decide chamar sua função | `toolTriggerSettings` |
| **Ação de fluxo de trabalho** | Uma etapa de fluxo de trabalho invoca sua função | `workflowActionTriggerSettings` |
| Disparador | Quando é executado | Configuração |
| ----------------------------- | --------------------------------------------------------- | ------------------------------- |
| **Rota HTTP** | Uma solicitação atinge a URL pública da sua função | `httpRouteTriggerSettings` |
| **Cron** | Uma expressão CRON corresponde | `cronTriggerSettings` |
| **Evento de banco de dados** | Um registro do workspace é criado, atualizado ou excluído | `databaseEventTriggerSettings` |
| **Ferramenta de IA** | Um recurso de IA do Twenty decide chamar sua função | `toolTriggerSettings` |
| **Ação de fluxo de trabalho** | Uma etapa de fluxo de trabalho invoca sua função | `workflowActionTriggerSettings` |
As funções são executadas em sandbox, em processos Node.js isolados, e acessam o workspace por meio de um cliente de API tipado, com escopo definido pelo papel declarado em [`defineApplication()`](/l/pt/developers/extend/apps/config/application).
@@ -4,7 +4,25 @@ description: comandos `yarn twenty` para executar funções, transmitir logs, ge
icon: terminal
---
Além de `dev`, `dev:build`, `dev:add` e `dev:typecheck`, a CLI `yarn twenty` fornece comandos para executar funções, visualizar logs e gerenciar instalações de aplicativos.
A CLI `yarn twenty` é sua interface para tudo relacionado a apps. Lista completa de comandos:
| Comando | O que faz | Documentado em |
| ----------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `dev` | Monitora arquivos-fonte e sincroniza alterações em tempo real | [Início rápido](/l/pt/developers/extend/apps/getting-started/quick-start) |
| `plan` | Visualize as alterações de metadados sem aplicá-las | [Sincronização e recuperação](/l/pt/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `apply` | Aplicar alterações de metadados após exibir o plano | [Sincronização e recuperação](/l/pt/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Compile o app e gere o cliente de API (`--tarball` para empacotar um `.tgz`) | [Publicação](/l/pt/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Executar verificação de tipos TypeScript | [Testes](/l/pt/developers/extend/apps/operations/testing) |
| `dev:add` | Criar o esqueleto de uma nova entidade | [Scaffolding](/l/pt/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Regenerar o cliente de API tipado | esta página |
| `dev:function:exec` / `dev:function:logs` | Executar funções e transmitir seus logs | esta página |
| `dev:translations-extract` | Extrair strings traduzíveis para catálogos em `locales/` | [Traduções](/l/pt/developers/extend/apps/translations/overview) |
| `dev:catalog-sync` | Acionar a sincronização do catálogo do marketplace | [Publicação](/l/pt/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Ciclo de vida de lançamento | [Publicação](/l/pt/developers/extend/apps/operations/publishing) e esta página |
| `docker:*` | Gerenciar o contêiner do servidor local Twenty | [Servidor local](/l/pt/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Gerenciar conexões de servidor | esta página |
Todo comando aceita `-r, --remote \<name>` para direcionar a um remoto específico em vez do padrão.
## Executando funções (`yarn twenty dev:function:exec`)
@@ -20,8 +38,9 @@ yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the post-install function
# Execute the install hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
## Visualizando logs de funções (`yarn twenty dev:function:logs`)
@@ -100,6 +119,12 @@ yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Suas credenciais são armazenadas em `~/.twenty/config.json`.
@@ -229,7 +229,7 @@ yarn twenty dev:catalog-sync
# yarn twenty dev:catalog-sync --remote production
```
Os metadados exibidos no marketplace vêm da sua configuração `defineApplication()` — campos como `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` e `termsUrl`.
Os metadados exibidos no marketplace vêm da sua configuração de `defineApplication()` — consulte [Metadados do marketplace](#marketplace-metadata) acima.
<Note>
Se o seu aplicativo não definir um `aboutDescription` em `defineApplication()`, o marketplace usará automaticamente o `README.md` do seu pacote no npm como conteúdo da página Sobre. Isso significa que você pode manter um único README tanto para o npm quanto para o marketplace da Twenty. Se quiser uma descrição diferente no marketplace, defina explicitamente `aboutDescription`.
@@ -15,33 +15,44 @@ Para a iteração local do dia a dia, quase sempre você vai querer `yarn twenty
| Você quer… | Comando | Notas |
| ------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Iterar localmente com sincronização em tempo real | `yarn twenty dev` | Monitora seus arquivos e sincroniza a cada alteração. |
| Sincronizar uma vez e sair (CI, scripts, hooks) | `yarn twenty dev --once` | Um build + sincronização, depois encerra. |
| Prever mudanças **sem aplicá-las** | `yarn twenty dev --once --dry-run` | Calcula e imprime o diff; não grava nada. |
| Sincronizar uma vez e sair (CI, scripts, hooks) | `yarn twenty apply` | Um build + sincronização, depois encerra. Adicione `--force` para pular a confirmação de mudança destrutiva. |
| Prever mudanças **sem aplicá-las** | `yarn twenty plan` | Calcula e imprime o diff; não grava nada. |
| Remover o app do workspace | `yarn twenty app:uninstall` | Adicione `--yes` para pular o prompt. |
| Enviar um tarball para um servidor | `yarn twenty app:publish --private` | Requer uma versão **estritamente maior** em `package.json` — veja [Publicação](/l/pt/developers/extend/apps/operations/publishing). |
| Publicar no marketplace (npm) | `yarn twenty app:publish` | — |
| Instalar / atualizar uma versão implantada | `yarn twenty app:install` | Instala a versão atualmente implantada. |
| Limpar o servidor local e começar do zero | `yarn twenty docker:reset` | Exclui **todos** os dados locais — último recurso. |
<Note>
`yarn twenty dev --once` e `yarn twenty dev --once --dry-run` ainda funcionam como aliases obsoletos para `yarn twenty apply` e `yarn twenty plan`.
</Note>
### A sincronização local não precisa de incremento de versão
A regra de `version` estritamente crescente (`VERSION_ALREADY_EXISTS` no deploy, `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` na instalação) se aplica a **`app:publish` / `app:install`** — o caminho de release. `yarn twenty dev` sincroniza seu manifesto no lugar e nunca exige mudança de versão, então você não precisa mexer em `package.json` para iterar. Se você se pegar aumentando a versão para testar uma mudança local, está usando o caminho de release quando o que quer é o ciclo de desenvolvimento.
## Lendo a saída da sincronização
Cada sincronização imprime as mudanças de metadados que aplicou (ou aplicaria, com `--dry-run`):
Cada sincronização imprime as alterações de metadados que aplicou (ou aplicaria, com `plan`), no estilo do Terraform — um bloco por entidade com seus atributos, depois uma linha de resumo:
```text filename="Terminal"
Metadata changes: 2 created, 1 updated, 1 deleted
created objectMetadata rocket
created fieldMetadata timelineActivities
updated fieldMetadata launchedAt
deleted pageLayout legacyTab
✓ Synced
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
Este é seu primeiro diagnóstico: ele mostra exatamente quais objetos, campos e layouts mudaram, para que você possa confirmar que uma sincronização fez o que esperava antes de conferir na interface.
Mudanças destrutivas (`to destroy`) são listadas com o que elas removem (por exemplo, `objectMetadata "auditNote" — drops the table and all its rows`) e exigem confirmação interativa, ou `--force` em scripts.
Quando uma sincronização falha em uma única entidade, o erro nomeia a entidade com problema e seu `universalIdentifier`, por exemplo:
```text
@@ -50,39 +61,42 @@ Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337)
Use esse identificador para encontrar a entidade no seu manifesto (e, se necessário, no workspace), em vez de adivinhar qual está em conflito.
## Visualizando mudanças (dry run)
## Visualizando mudanças (plan)
`yarn twenty dev --once --dry-run` compila seu manifesto, pede ao servidor o plano de migração e o imprime — **sem aplicar nada**. É a forma segura de responder "o que esta sincronização mudaria?" antes de se comprometer com ela.
`yarn twenty plan` compila seu manifesto, pede ao servidor o plano de migração e o imprime — **sem aplicar nada**. É a forma segura de responder "o que esta sincronização mudaria?" antes de se comprometer com ela.
```bash filename="Terminal"
yarn twenty dev --once --dry-run
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata diff (dry run, nothing will be applied)...
Metadata changes: 1 created, 1 updated
created fieldMetadata timelineActivities
updated objectMetadata rocket
✓ Dry run complete for My App — no changes were applied
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "timelineActivities" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
Um dry run:
Um plano:
* **Não grava nada** — nenhuma migração de metadados, nenhuma atualização de registro de aplicativo, nenhuma mudança de função/aba padrão e nenhuma geração de cliente de API.
* Retorna o **mesmo diff** que uma sincronização real aplicaria, para que você possa revisar previamente as entidades criadas/atualizadas/excluídas.
* É útil antes de uma mudança arriscada, ao revisar uma mudança gerada por IA ou em um script que deve falhar se uma mudança inesperada estiver prestes a ser aplicada.
<Note>
Um dry run só antevê mudanças de **metadados**, e exige que o app tenha sido sincronizado ao menos uma vez (para que o workspace o conheça). Se você rodar isso em um app que nunca foi sincronizado, o servidor informa que o app não está instalado — rode `yarn twenty dev` uma vez antes.
Um plano só antevê mudanças de **metadados**, e exige que o app tenha sido sincronizado ao menos uma vez (para que o workspace o conheça). Se você rodar isso em um app que nunca foi sincronizado, o servidor informa que o app não está instalado — rode `yarn twenty dev` uma vez antes.
</Note>
## Escada de recuperação
Quando os metadados locais parecerem errados, aumente o nível nesta ordem e pare assim que estiver desbloqueado. Cada etapa é mais disruptiva que a anterior.
1. **Ressincronizar.** Rode `yarn twenty dev --once` novamente. Sincronizações são idempotentes — rodar novamente um manifesto limpo é seguro e frequentemente resolve um problema transitório.
2. **Prever o plano.** Rode `yarn twenty dev --once --dry-run` para ver exatamente o que a próxima sincronização pretende mudar, sem aplicá-la.
1. **Ressincronizar.** Rode `yarn twenty apply` novamente. Sincronizações são idempotentes — rodar novamente um manifesto limpo é seguro e frequentemente resolve um problema transitório.
2. **Prever o plano.** Rode `yarn twenty plan` para ver exatamente o que a próxima sincronização pretende mudar, sem aplicá-la.
3. **Leia o erro nomeado.** Se uma sincronização falhar, anote o tipo de metadado e o `universalIdentifier` na mensagem (veja acima) e localize essa entidade no seu manifesto. Um conflito geralmente aponta para um identificador duplicado ou reutilizado.
4. **Desinstalar e reinstalar.** `yarn twenty app:uninstall`, depois sincronize novamente (`yarn twenty dev`). Isso reconstrói os metadados do app a partir do zero, mantendo o restante do seu workspace intacto.
5. **Reset completo (último recurso).** `yarn twenty docker:reset`, depois faça o seeding e a sincronização novamente.
@@ -78,6 +78,13 @@ Crie um `vitest.config.ts` na raiz do seu aplicativo:
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
@@ -88,66 +95,74 @@ export default defineConfig({
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
setupFiles: ['src/__tests__/setup-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL: 'http://localhost:2020',
TWENTY_API_KEY: 'your-api-key',
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Crie um arquivo de configuração que verifique se o servidor está acessível antes da execução dos testes:
Crie um arquivo de configuração global que verifique se o servidor está acessível, escreva uma configuração de teste para o SDK (`~/.twenty/config.test.json`) e sincronize o app antes da execução dos testes:
```ts src/__tests__/setup-test.ts
```ts src/__tests__/global-setup.ts
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { beforeAll } from 'vitest';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
beforeAll(async () => {
// Verify the server is running
const response = await fetch(`${TWENTY_API_URL}/healthz`);
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
'Start the server before running integration tests.',
);
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write a temporary config for the SDK
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(TEST_CONFIG_DIR, 'config.json'),
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: {
local: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_API_KEY,
},
},
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
});
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## APIs programáticas do SDK
O subcaminho `twenty-sdk/cli` exporta funções que você pode chamar diretamente a partir do código de teste:
| Função | Descrição |
| -------------- | ------------------------------------------------------------ |
| `appBuild` | Compilar o aplicativo e, opcionalmente, empacotar um tarball |
| `appDeploy` | Enviar um tarball para o servidor |
| `appInstall` | Instalar o aplicativo no espaço de trabalho ativo |
| `appUninstall` | Desinstalar o aplicativo do espaço de trabalho ativo |
| Função | Descrição |
| -------------- | ---------------------------------------------------------------- |
| `appBuild` | Compilar o aplicativo e, opcionalmente, empacotar um tarball |
| `appDeploy` | Enviar um tarball para o servidor |
| `appDevOnce` | Compila e sincroniza o app uma vez (igual a `yarn twenty apply`) |
| `appInstall` | Instalar o aplicativo no espaço de trabalho ativo |
| `appUninstall` | Desinstalar o aplicativo do espaço de trabalho ativo |
Cada função retorna um objeto de resultado com `success: boolean` e `data` ou `error`.
@@ -238,64 +253,10 @@ Você também pode executar a verificação de tipos no seu aplicativo sem execu
yarn twenty dev:typecheck
```
Isso executa `tsc --noEmit` e informa quaisquer erros de tipo.
Isso executa `tsc --noEmit` no `tsconfig.json` do seu app e informa quaisquer erros de tipo. Os apps criados pelo scaffold também incluem um script `yarn typecheck` que cobre arquivos de teste também (`tsconfig.spec.json`).
## CI com GitHub Actions
O gerador de scaffold cria um workflow do GitHub Actions pronto para uso em `.github/workflows/ci.yml`. Ele executa seus testes de integração automaticamente a cada push para `main` e em pull requests.
O gerador de scaffold cria um workflow pronto para uso em `.github/workflows/ci.yml`. A cada push para `main` e a cada pull request, ele inicia um servidor Twenty efêmero no runner (por meio da action `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`) e então executa `yarn lint`, `yarn typecheck`, `yarn test:unit` e `yarn test` com `TWENTY_API_URL` / `TWENTY_API_KEY` apontando para esse servidor. Nenhum secret é necessário e você pode fixar a versão do servidor por meio da variável de ambiente `TWENTY_VERSION` no topo do workflow.
O workflow:
1. Faz checkout do seu código
2. Inicializa um servidor Twenty temporário usando a ação `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Instala as dependências com `yarn install --immutable`
4. Executa `yarn test` com `TWENTY_API_URL` e `TWENTY_API_KEY` injetados a partir das saídas da ação
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
```
Você não precisa configurar nenhum segredo — a ação `spawn-twenty-docker-image` inicia um servidor Twenty efêmero diretamente no runner e fornece os detalhes de conexão. O segredo `GITHUB_TOKEN` é fornecido automaticamente pelo GitHub.
Para fixar uma versão específica do Twenty em vez de `latest`, altere a variável de ambiente `TWENTY_VERSION` no topo do workflow.
Consulte [Publicação → CI/CD automatizado](/l/pt/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) para um passo a passo completo de ambos os workflows criados pelo scaffold (`ci.yml` e o pipeline de deploy `cd.yml`).
@@ -91,9 +91,11 @@ const GenerateDocumentForm = () => {
}, []);
const generate = async () => {
const apiBaseUrl = process.env.TWENTY_API_URL;
// Prefer the injected functions URL; fall back to the legacy /s prefix (self-hosted/local)
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`;
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
const res = await fetch(`${functionsBaseUrl}/documents/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ templateId, recordId }),
@@ -186,7 +188,9 @@ const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;
const functionsBaseUrl =
process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL ?? ''}/s`;
const webUrl = `${functionsBaseUrl}/documents/view?id=${recordId}`;
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
@@ -9,8 +9,15 @@ O mesmo manipulador também pode responder solicitações HTTP. Vamos adicionar
* um terminal **POST** aponta as chamadas da UI para gerar um documento e
* um endpoint de **GET** público que renderiza um documento como uma página web impressa.
Ambos usam `httpRouteTriggerSettings`. As rotas de aplicativos são servidas em `/s` no seu servidor
Vinte (por exemplo, `http://localhost:2020/s/documents/generate`).
Ambos usam `httpRouteTriggerSettings`. No servidor local de desenvolvimento, as rotas de aplicativos são
servidas sob o prefixo `/s` (por exemplo, `http://localhost:2020/s/documents/generate`).
<Note>
Em Vinte nuvens, as rotas são servidas no domínio de funções dedicadas do espaço de trabalho
— a URL de 20 injeções como `TWENTY_FUNCTIONS_URL`, sem prefixo `/s`. O prefixo `/s`
está obsoleto e só permanece para instâncias auto-hospedadas e locais.
Ver [Chamando uma função lógica](/l/pt/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
## Rota POST - gerar sob demanda
@@ -77,11 +77,11 @@ Executar os mesmos portões CI do portão:
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty dev --once --dry-run # preview the metadata diff
yarn twenty plan # preview the metadata diff
```
A corrida seca imprime exatamente o que mudaria no servidor sem aplicá-lo
uma boa verificação de sanidade final. Veja
O plano mostra exatamente o que mudaria no servidor sem aplicar as alterações
um bom último teste de sanidade. Veja
[Testing](/l/pt/developers/extend/apps/operations/testing) e
[Sincronização e recuperação](/l/pt/developers/extend/apps/operations/sync-and-recovery).