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:
committed by
GitHub
parent
a0cf4cc9e1
commit
ebee7d71b9
@@ -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}'` ``.
|
||||
|
||||
+32
-16
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -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`).
|
||||
|
||||
+7
-3
@@ -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
-2
@@ -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
|
||||
|
||||
|
||||
+3
-3
@@ -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).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user