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: Rulați logică înainte sau după instalare — pentru a popula cu
icon: wrench
---
Hook-urile de instalare sunt funcții logice speciale care rulează în timpul ciclului de viață al instalării sau actualizării. Acestea folosesc același runtime de handler ca și [funcțiile logice](/l/ro/developers/extend/apps/logic/logic-functions) obișnuite și primesc un `InstallPayload`, dar sunt declarate cu propriile lor funcții de definire — `definePostInstallLogicFunction()` și `definePreInstallLogicFunction()` — și există în afara modelului obișnuit de declanșatori (HTTP, cron, evenimente de bază de date).
Hook-urile de instalare sunt funcții logice speciale care rulează în timpul ciclului de viață al instalării sau actualizării. Acestea folosesc același runtime de handler ca și [funcțiile logice](/l/ro/developers/extend/apps/logic/logic-functions) obișnuite și primesc un `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` este `undefined` la o instalare nouă), dar sunt declarate cu propriile lor funcții de definire și există în afara modelului obișnuit de declanșatori (HTTP, cron, evenimente de bază de date).
Fiecare aplicație poate defini **cel mult o funcție de pre-instalare** și **cel mult o funcție de post-instalare**. Construirea manifestului va genera o eroare dacă se detectează mai mult de una din oricare dintre ele.
@@ -19,111 +19,59 @@ Fiecare aplicație poate defini **cel mult o funcție de pre-instalare** și **c
└─────────────────────────────────────────────────────────────┘
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Rulează după ce migrarea metadatelor workspace-ului este aplicată">
## Dintr-o privire
O funcție de post-instalare rulează automat după ce aplicația a terminat de instalat într-un spațiu de lucru. Serverul o execută **după** ce metadatele aplicației au fost sincronizate și clientul SDK a fost generat, astfel încât spațiul de lucru este complet pregătit pentru utilizare, iar noua schemă este disponibilă. Cazuri tipice de utilizare includ popularea cu date implicite, crearea de înregistrări inițiale, configurarea setărilor spațiului de lucru sau provizionarea resurselor în cadrul serviciilor terților.
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ---------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Rulări | Înainte de migrarea metadatelor — schema și datele **anterioare** sunt încă intacte | După migrare și generarea SDK — schema **nouă** este aplicată |
| Execuție | Întotdeauna sincronă; blochează instalarea | Async în mod implicit (pus în coadă, 3 reîncercări); execuție sincronă opțională prin `shouldRunSynchronously: true` |
| La eșec | Instalarea este **întreruptă** înainte de orice modificare a schemei | Async: reîncercată de până la 3 ori. Sync: apelantul primește `POST_INSTALL_ERROR` (modificările de schemă **nu** sunt anulate) |
| Utilizare tipică | Faceți backup sau reparați date pe care o migrare le-ar pierde; refuzați un upgrade riscant aruncând o eroare | Populați date implicite, configurați workspace-ul, înregistrați resurse externe |
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
**Regulă generală:** folosiți implicit post-install. Apelați la pre-install doar când migrarea în sine este distructivă și trebuie să interceptați starea anterioară înainte să dispară.
const handler = async (payload: InstallPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
| Doriți să... | Folosiți |
| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Populați date, configurați workspace-ul, înregistrați resurse externe | `post-install` |
| Muncă de durată care nu ar trebui să blocheze răspunsul la instalare | `post-install` (mod async implicit, cu reîncercări ale workerului) |
| Configurare rapidă de care apelantul are nevoie imediat după ce instalarea se încheie | `post-install` cu `shouldRunSynchronously: true` |
| Citiți sau faceți backup datelor pe care migrarea iminentă le-ar pierde | `pre-install` |
| Respingeți o actualizare care ar corupe datele existente | `pre-install` (aruncați din handler) |
| Reconciliere la fiecare upgrade | Oricare hook cu `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,
});
```
## Comportament partajat de ambele hook-uri
Puteți, de asemenea, să executați manual funcția post-instalare oricând folosind CLI:
* Configurația este o configurație `defineLogicFunction` minus setările de declanșare, plus `shouldRunOnVersionUpgrade`.
* **Când rulează**: doar la instalări noi, în mod implicit. Setați `shouldRunOnVersionUpgrade: true` pentru a rula și la upgrade-uri. Folosiți `previousVersion` / `newVersion` pentru a ramifica în funcție de calea de upgrade.
* **Idempotența contează**: post-install async poate fi reîncercat, iar oricare hook rulează din nou la upgrade-uri când `shouldRunOnVersionUpgrade` este activat.
* Mediul obișnuit de logic-function (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) este injectat, astfel încât puteți apela Twenty API cu tokenul aplicației voastre.
* Hook-ul este atașat automat la manifestul aplicației la build (`preInstallLogicFunction` / `postInstallLogicFunction`) — nu este nevoie să fie referențiat în [`defineApplication()`](/l/ro/developers/extend/apps/config/application).
* Valoarea implicită pentru `timeoutSeconds` este 300 pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor.
* **Nu este executat în modul dev**: `yarn twenty dev` sare peste fluxul de instalare și sincronizează fișierele direct, astfel încât hook-urile nu rulează acolo. Declanșați-le manual în schimb:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
```
Puncte cheie:
* Funcțiile de post-instalare folosesc `definePostInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `toolTriggerSettings`, `workflowActionTriggerSettings`).
* Handlerul primește un `InstallPayload` cu `{ previousVersion?: string; newVersion: string }` — `newVersion` este versiunea care este instalată, iar `previousVersion` este versiunea instalată anterior (sau `undefined` la o instalare nouă). Folosiți aceste valori pentru a distinge instalările noi de actualizări și pentru a rula logică de migrare specifică versiunii.
* **Când rulează hook-ul**: doar la instalări noi, în mod implicit. Transmiteți `shouldRunOnVersionUpgrade: true` dacă doriți să ruleze și atunci când aplicația este actualizată de la o versiune anterioară. Când este omis, indicatorul are implicit valoarea `false`, iar actualizările sar peste hook.
* **Model de execuție — implicit asincron, sincron opțional**: indicatorul `shouldRunSynchronously` controlează *modul în care* este executat post-install.
* `shouldRunSynchronously: false` *(implicit)* — hook-ul este **pus în coadă în message queue** cu `retryLimit: 3` și rulează asincron într-un worker. Răspunsul la instalare revine imediat ce jobul este pus în coadă, astfel încât un handler lent sau care eșuează nu blochează apelantul. Workerul va reîncerca de până la trei ori. **Folosiți acest mod pentru joburi de lungă durată** — popularea unor seturi mari de date, apelarea API-urilor lente ale terților, provizionarea resurselor externe, orice ar putea depăși o fereastră rezonabilă de răspuns HTTP.
* `shouldRunSynchronously: true` — hook-ul este executat **inline în timpul fluxului de instalare** (același executor ca pre-install). Cererea de instalare blochează până când handlerul se termină, iar dacă acesta aruncă o eroare, apelantul instalării primește un `POST_INSTALL_ERROR`. Fără reîncercări automate. **Folosiți acest mod pentru sarcini rapide, care trebuie să se finalizeze înainte de răspuns** — de exemplu, emiterea unei erori de validare către utilizator sau o configurare rapidă de care clientul va depinde imediat după ce apelul de instalare revine. Reține că migrarea metadatelor a fost deja aplicată până când rulează post-install, astfel încât un eșec în modul sincron **nu** anulează modificările de schemă — doar expune eroarea.
* Asigurați-vă că handlerul dvs. este idempotent. În modul asincron, coada poate reîncerca de până la trei ori; în oricare mod, hook-ul poate rula din nou la actualizări când `shouldRunOnVersionUpgrade: true`.
* Variabilele de mediu `APPLICATION_ID`, `APP_ACCESS_TOKEN` și `API_URL` sunt disponibile în interiorul handlerului (la fel ca în orice altă funcție logică), astfel încât puteți apela API-ul Twenty cu un token de acces al aplicației limitat la aplicația dvs.
* Este permisă o singură funcție de post-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una.
* `universalIdentifier`, `shouldRunOnVersionUpgrade` și `shouldRunSynchronously` ale funcției sunt atașate automat la manifestul aplicației în câmpul `postInstallLogicFunction` în timpul build-ului — nu este nevoie să le referiți în [`defineApplication()`](/l/ro/developers/extend/apps/config/application).
* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor.
* **Nu se execută în modul dev**: când o aplicație este înregistrată local (prin `yarn twenty dev`), serverul sare complet peste fluxul de instalare și sincronizează fișierele direct prin watcher-ul CLI — astfel încât post-install nu rulează niciodată în modul dev, indiferent de `shouldRunSynchronously`. Folosiți `yarn twenty dev:function:exec --postInstall` pentru a-l declanșa manual într-un workspace care rulează.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Rulează înainte ca migrarea metadatelor workspace-ului să fie aplicată">
O funcție de pre-instalare rulează automat în timpul instalării, **înainte ca migrarea metadatelor workspace-ului să fie aplicată**. Are aceeași structură a payload-ului ca post-install (`InstallPayload`), dar este plasată mai devreme în fluxul de instalare, astfel încât poate pregăti starea de care depinde migrarea iminentă — utilizări tipice includ realizarea unui backup al datelor, validarea compatibilității cu noua schemă sau arhivarea înregistrărilor care urmează să fie restructurate sau eliminate.
```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,
});
```
Puteți, de asemenea, să executați manual funcția de pre-instalare oricând folosind CLI:
```bash filename="Terminal"
yarn twenty dev:function:exec --preInstall
```
Puncte cheie:
* Funcțiile de pre-instalare folosesc `definePreInstallLogicFunction()` — aceeași configurare specializată ca pentru post-install, doar că atașată la un alt punct din ciclul de viață.
* Atât handlerele de pre-install, cât și cele de post-install primesc același tip `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importați-l o singură dată și reutilizați-l pentru ambele hook-uri.
* **Când rulează hook-ul**: poziționat chiar înainte de migrarea metadatelor workspace-ului (`synchronizeFromManifest`). Înainte de execuție, serverul rulează un "sync redus", pur aditiv, care înregistrează funcția de pre-instalare a versiunii **noi** în metadatele workspace-ului — nimic altceva nu este atins — și apoi o execută. Deoarece acest sync este doar aditiv, obiectele, câmpurile și datele versiunii precedente sunt încă intacte când rulează handlerul dvs.: puteți citi și face backup în siguranță stării pre-migrare.
* **Model de execuție**: pre-install este executat **sincron** și **blochează instalarea**. Dacă handlerul aruncă o eroare, instalarea este întreruptă înainte ca orice modificări de schemă să fie aplicate — workspace-ul rămâne la versiunea anterioară într-o stare consistentă. Acest lucru este intenționat: pre-install este ultima dvs. șansă de a refuza o actualizare riscantă.
* La fel ca la post-install, este permisă o singură funcție de pre-instalare per aplicație. Este atașată automat la manifestul aplicației sub `preInstallLogicFunction` în timpul build-ului.
* **Nu se execută în modul dev**: la fel ca post-install — fluxul de instalare este sărit complet pentru aplicațiile înregistrate local, astfel încât pre-install nu rulează niciodată sub `yarn twenty dev`. Folosiți `yarn twenty dev:function:exec --preInstall` pentru a-l declanșa manual.
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Rulează după ce migrarea metadatelor workspace-ului este aplicată">
</Accordion>
<Accordion title="Pre-install vs post-install: când să folosiți fiecare" description="Alegerea hook-ului de instalare potrivit">
Ambele hook-uri fac parte din același flux de instalare și primesc același `InstallPayload`. Diferența constă în **momentul** în care rulează în raport cu migrarea metadatelor workspace-ului, iar asta schimbă ce date pot atinge în siguranță.
Pre-install este întotdeauna **sincron** (blochează instalarea și o poate întrerupe). Post-install este **implicit asincron** — pus în coadă pe un worker cu reîncercări automate — dar poate opta pentru execuție sincronă cu `shouldRunSynchronously: true`. Consultați acordeonul `definePostInstallLogicFunction` de mai sus pentru când să folosiți fiecare mod.
**Folosiți `post-install` pentru orice are nevoie ca noua schemă să existe.** Acesta este cazul obișnuit:
* Popularea datelor implicite (crearea înregistrărilor inițiale, a vizualizărilor implicite, a conținutului demo) pentru obiectele și câmpurile adăugate recent.
* Înregistrarea webhook-urilor la servicii terțe, acum că aplicația are acreditările sale.
* Apelarea propriului dvs. API pentru a finaliza configurarea care depinde de metadatele sincronizate.
* Logică idempotentă de tipul "asigurați-vă că acest lucru există" care ar trebui să reconcilieze starea la fiecare actualizare — combină cu `shouldRunOnVersionUpgrade: true`.
Exemplu — populează o înregistrare `PostCard` implicită după instalare:
Rulează după ce aplicația voastră a terminat instalarea: metadate sincronizate, clientul SDK generat, noua schemă poate fi interogată. Exemplu — populează o înregistrare implicită la instalări noi:
```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,
});
```
**Folosiți `pre-install` atunci când o migrare altfel ar distruge sau ar corupe datele existente.** Deoarece pre-install rulează pe schema *anterioară* și eșecul său anulează actualizarea, acesta este locul potrivit pentru orice este riscant:
Flag-ul `shouldRunSynchronously` controlează modelul de execuție:
* **Crearea unui backup al datelor care urmează să fie eliminate sau restructurate** — de exemplu, eliminați un câmp în v2 și trebuie să-i copiați valorile într-un alt câmp sau să le exportați în stocare înainte de rularea migrării.
* **Arhivarea înregistrărilor pe care o nouă constrângere le-ar invalida** — de exemplu, un câmp devine `NOT NULL` și trebuie mai întâi să ștergeți sau să corectați rândurile cu valori nule.
* **Validarea compatibilității și refuzarea actualizării dacă datele curente nu pot fi migrate fără probleme** — aruncați din handler și instalarea se oprește fără ca modificări să fie aplicate. Aceasta este mai sigur decât să descoperi incompatibilitatea în mijlocul migrării.
* **Redenumirea sau schimbarea cheilor datelor** înaintea unei modificări de schemă care ar pierde asocierile.
* `false` *(implicit)* — pus în coada de mesaje (`retryLimit: 3`) și rulat de un worker. Răspunsul la instalare este returnat imediat ce jobul este pus în coadă. **Folosiți pentru muncă de durată** — popularea unor seturi mari de date, API-uri lente ale terților.
* `true` — executat inline în timpul fluxului de instalare. Requestul de instalare este blocat până când handlerul se termină; o eroare aruncată este expusă apelantului ca `POST_INSTALL_ERROR` (fără reîncercări). **Folosiți pentru muncă rapidă, care trebuie să fie finalizată înainte de răspuns.** Migrarea a fost deja aplicată în acest punct, astfel încât un eșec nu anulează modificările de schemă — doar expune eroarea.
Exemplu — arhivează înregistrări înainte de o migrare distructivă:
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Rulează înainte ca migrarea metadatelor workspace-ului să fie aplicată">
Rulează înainte de migrarea metadatelor, pe schema **anterioară** — locul potrivit pentru a face backup datelor pe care o migrare le-ar pierde sau pentru a refuza un upgrade riscant. Înainte de execuție, serverul rulează un „sync redus”, pur aditiv, care înregistrează doar funcția de pre-instalare a versiunii noi; tot restul — obiectele, câmpurile și datele versiunii anterioare — rămâne neatins atunci când rulează handlerul.
Pre-install este întotdeauna **sincron** și blochează instalarea. Dacă handlerul aruncă o eroare, instalarea este întreruptă înainte de orice modificare a schemei — workspace-ul rămâne la versiunea anterioară într-o stare consistentă. Acest lucru este intenționat: pre-install este ultima dvs. șansă de a refuza o actualizare riscantă.
Exemplu — copiați valorile unui câmp vechi înainte ca migrarea să îl elimine:
```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({
});
```
**Regulă practică:**
| Doriți să... | Folosiți |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| Populați date implicite, configurați workspace-ul, înregistrați resurse externe | `post-install` |
| Rulați populări de durată sau apeluri către terți care nu ar trebui să blocheze răspunsul la instalare | `post-install` (implicit — `shouldRunSynchronously: false`, cu reîncercări ale workerului) |
| Rulați o configurare rapidă de care apelantul va depinde imediat după ce apelul de instalare revine | `post-install` cu `shouldRunSynchronously: true` |
| Citiți sau faceți backup datelor pe care migrarea iminentă le-ar pierde | `pre-install` |
| Respingeți o actualizare care ar corupe datele existente | `pre-install` (aruncați din handler) |
| Rulați o reconciliere la fiecare actualizare | `post-install` cu `shouldRunOnVersionUpgrade: true` |
| Faceți o configurare unică doar la prima instalare | `post-install` cu `shouldRunOnVersionUpgrade: false` (implicit) |
<Note>
Dacă aveți dubii, alegeți implicit **post-install**. Apelați la pre-install doar când migrarea în sine este distructivă și trebuie să interceptați starea anterioară înainte să dispară.
</Note>
</Accordion>
</AccordionGroup>
@@ -86,6 +86,22 @@ export default defineObject({
**Câmpurile de bază sunt adăugate automat.** Când definiți un obiect personalizat, Twenty creează pentru dvs. câmpuri standard precum `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` și `deletedAt`. Nu trebuie să le declarați în tabloul `fields` — doar câmpurile dvs. personalizate. Puteți suprascrie un câmp implicit declarând unul cu același nume, dar acest lucru este rareori o idee bună.
</Note>
## Tipuri de câmpuri
Setul complet de valori `FieldType`, exportate din `twenty-sdk/define`:
| Categorie | Tipuri |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Text | `TEXT`, `RICH_TEXT`, `ARRAY` (de șiruri), `RAW_JSON` |
| Numerice | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (precizie arbitrară), `RATING`, `POSITION` |
| Date calendaristice | `DATE`, `DATE_TIME` |
| Alegere | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Compuse | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identificatori și relații | `UUID`, `RELATION`, `MORPH_RELATION` (vezi [Relații](/l/ro/developers/extend/apps/data/relations)) |
| Sistem | `TS_VECTOR` (vector de căutare full-text, gestionat de server) |
Tipurile compuse stochează mai multe subcâmpuri (de ex. `FULL_NAME` = prenume + nume de familie; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` și `MULTI_SELECT` necesită un tablou `options`, ca în exemplul de mai sus.
## Valori implicite
Valorile implicite de tip șir literal trebuie să fie încadrate în ghilimele simple **în interiorul** șirului — `defaultValue: "'Draft'"`, nu `defaultValue: "Draft"`. De aceea câmpul `status` de mai sus folosește `` `'${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
```
## Fișiere cheie
| Fișier / Folder | Scop |
| ---------------------------------------- | -------------------------------------------------------------------- |
| `src/application-config.ts` | **Necesar.** Fișierul principal de configurare pentru aplicație. |
| `src/default-role.ts` | Rol implicit care controlează la ce pot avea acces funcțiile logice. |
| `src/constants/universal-identifiers.ts` | UUID-uri generate automat și metadate (nume afișat, descriere). |
| `src/__tests__/` | Teste de integrare (configurare + test de exemplu). |
| `public/` | Resurse statice (imagini, fonturi) servite împreună cu aplicația. |
| Fișier / Folder | Scop |
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Necesar.** Fișierul principal de configurare pentru aplicație. |
| `src/default-role.ts` | Rol implicit care controlează la ce pot avea acces funcțiile logice. |
| `src/constants/universal-identifiers.ts` | UUID-uri generate automat și metadate (nume afișat, descriere). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | O pagină de bun venit de pornire: un front component redat de un page layout autonom, accesibilă din bara laterală. |
| `src/__tests__/` | Un test unitar plus un test de integrare (cu configurarea sa globală) care sincronizează aplicația cu un server real. |
| `public/` | Resurse statice (imagini, fonturi) servite împreună cu aplicația. |
| `AGENTS.md` / `CLAUDE.md` | Ghid pentru agenții AI de programare care lucrează la aplicație. |
<Note>
**Organizarea fișierelor ține de dvs.** Folderele de mai sus sunt convenții — SDK-ul detectează entitățile prin analiză AST pe apelurile `export default defineEntity(...)`, indiferent unde se află fișierul.
@@ -47,15 +60,18 @@ Ambele pachete Twenty SDK trebuie plasate sub `devDependencies`, nu sub `depende
{
"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"
}
}
```
Generatorul de schelete fixează versiunile `twenty-sdk` și `twenty-client-sdk` la propria sa versiune — păstrează-le sincronizate când faci upgrade.
* **`twenty-sdk`** livrează CLI-ul `twenty` și uneltele de build/scaffolding. Acesta rulează doar în timpul dezvoltării și al build-ului și nu este niciodată importat de runtime-ul aplicației tale publicate.
* **`twenty-client-sdk`** este importat de codul aplicației tale (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), dar Twenty îl furnizează la runtime — funcțiile de logică îl obțin dintr-un strat SDK generat, iar componentele de interfață îl rezolvă din module livrate de server. Copia instalată local este folosită doar pentru verificarea tipurilor și pentru build-ul la momentul de deploy, astfel că nu trebuie niciodată inclusă în bundle-ul livrat.
Păstrarea oricărui pachet sub `dependencies` îl include în bundle-ul de runtime al aplicației instalate, unde reprezintă o încărcătură inutilă. `twenty build` emite un avertisment atunci când oricare dintre ele este încă listat sub `dependencies`.
Păstrarea oricărui pachet sub `dependencies` îl include în bundle-ul de runtime al aplicației instalate, unde reprezintă o încărcătură inutilă. `twenty dev:build` emite un avertisment atunci când oricare dintre ele este încă listat sub `dependencies`.
Adaugă dependențele de runtime proprii ale aplicației tale (bibliotecile pe care funcțiile tale de logică chiar le importă la runtime) sub `dependencies`, ca de obicei.
@@ -6,17 +6,17 @@ description: Creați prima dvs. aplicație Twenty în câteva minute.
## Cerințe
* **Node.js 24+** — [Descărcați](https://nodejs.org/)
* **Node.js 24.5+** — [Descărcați](https://nodejs.org/)
* **Yarn 4** — vine împreună cu Node prin Corepack. Activați-l: `corepack enable`
* **Docker** — [Descărcați](https://www.docker.com/products/docker-desktop/). Necesar pentru a rula un server Twenty local. Omiteți dacă rulați deja Twenty în altă parte.
Crearea unei aplicații Twenty are trei faze. Generatorul le reunește într-o singură comandă pe calea optimă, dar fiecare fază este un concept separat — când ceva eșuează, dacă știți în ce fază sunteți, știți ce trebuie să corectați.
| Fază | Ce faceți | Instrument | Rezultat |
| ----------------------- | ------------------------------------------------ | ----------------------------- | ------------------------------ |
| **1. Creați scheletul** | Generați codul sursă al aplicației | `npx create-twenty-app` | Un proiect TypeScript pe disc |
| **2. Rulați un server** | Porniți un server Twenty cu care să sincronizați | Docker + `yarn twenty server` | O instanță Twenty care rulează |
| **3. Sincronizați** | Sincronizați în timp real codul cu serverul | `yarn twenty dev` | Modificările apar în UI |
| Fază | Ce faceți | Instrument | Rezultat |
| ----------------------- | ------------------------------------------------ | ----------------------------------- | ------------------------------ |
| **1. Creați scheletul** | Generați codul sursă al aplicației | `npx create-twenty-app` | Un proiect TypeScript pe disc |
| **2. Rulați un server** | Porniți un server Twenty cu care să sincronizați | Docker + `yarn twenty docker:start` | O instanță Twenty care rulează |
| **3. Sincronizați** | Sincronizați în timp real codul cu serverul | `yarn twenty dev` | Modificările apar în UI |
---
@@ -28,7 +28,7 @@ Creați o nouă aplicație din șablon:
npx create-twenty-app@latest my-twenty-app
```
Vi se va cere un nume și o descriere — apăsați **Enter** pentru valorile implicite. Aceasta generează un proiect TypeScript în `my-twenty-app/` cu un fișier inițial `application-config.ts`, un rol implicit, un flux de lucru CI și un test de integrare.
Generatorul este neinteractiv: numele directorului devine numele aplicației. Transmite `--display-name` și `--description` pentru a personaliza metadatele generate (le poți edita și mai târziu în `src/constants/universal-identifiers.ts`). Aceasta generează un proiect TypeScript în `my-twenty-app/` cu un fișier inițial `application-config.ts`, un rol implicit, fluxuri de lucru CI/CD și un test de integrare.
**După această fază:** aveți codul sursă al aplicației pe mașina dvs. Încă nu rulează — aceasta este Faza 2.
@@ -38,28 +38,14 @@ Vi se va cere un nume și o descriere — apăsați **Enter** pentru valorile im
Aplicația are nevoie de un server Twenty cu care să se sincronizeze. Serverul este o instanță Twenty completă — UI, API GraphQL, PostgreSQL — care rulează local în Docker. Codul local încarcă definițiile pe acel server, făcându-le să apară în UI.
Generatorul de schelet vă propune să pornească unul pentru dvs.:
Generatorul de proiecte pornește unul pentru tine: cu Docker rulând, descarcă imaginea `twentycrm/twenty-app-dev`, o pornește pe portul `2020` și autentifică CLI-ul față de spațiul de lucru demo preconfigurat (`tim@apple.dev`) — fără a fi necesară autentificarea.
> **Doriți să configurați o instanță Twenty locală?**
* **Yes (recomandat)** — descarcă imaginea Docker `twentycrm/twenty-app-dev` și o pornește pe portul `2020`. Asigurați-vă mai întâi că Docker rulează.
* **No** — alegeți această opțiune dacă aveți deja un server Twenty la care doriți să vă conectați. Îl puteți conecta ulterior cu `yarn twenty remote:add`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Porniți instanța locală?" />
</div>
După ce serverul pornește, se deschide un browser pentru autentificare. Folosiți contul demo preconfigurat:
* **E-mail:** `tim@apple.dev`
* **Parolă:** `tim@apple.dev`
Pentru a te conecta în schimb la un server Twenty existent, treci argumentul `--url \<your-server-url>`. Serverele la distanță se autentifică prin OAuth: se deschide un browser astfel încât să te poți autentifica și să dai clic pe **Authorize**, ceea ce oferă CLI-ului acces la spațiul tău de lucru. (Poți opta pentru OAuth și local cu `--authentication-method oauth` — autentifică-te cu `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Ecranul de autentificare Twenty" />
</div>
Faceți clic pe **Authorize** pe ecranul următor — aceasta oferă CLI-ului acces la spațiul dvs. de lucru.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Ecranul de autorizare Twenty CLI" />
</div>
@@ -117,27 +103,31 @@ Faceți clic pe **View installed app** pentru a vedea instalarea în spațiul de
### Sincronizare unică pentru CI și scripturi
Adăugați `--once` pentru a rula un singur build + sync și a ieși — același flux, fără watcher:
Folosește `plan` și `apply` pentru a rula același pipeline o singură dată, fără 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
```
| Comandă | Comportament | Când se folosește |
| ---------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| `yarn twenty dev` | Monitorizează și resincronizează la fiecare modificare. Rulează până când îl opriți. | Dezvoltare locală interactivă. |
| `yarn twenty dev --once` | Un singur build + sync, iese cu `0` la succes, `1` la eșec. | CI, hook-uri pre-commit, agenți AI, fluxuri de lucru scriptate. |
| `yarn twenty dev --once --dry-run` | Construiește și afișează modificările de metadate **fără a le aplica**. | Inspectarea modificărilor pe care le-ar face o sincronizare înainte de a le confirma. |
| Comandă | Comportament | Când se folosește |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `yarn twenty dev` | Monitorizează și resincronizează la fiecare modificare. Rulează până când îl opriți. | Dezvoltare locală interactivă. |
| `yarn twenty apply` | Un singur build + sync, iese cu `0` la succes, `1` la eșec. Solicită confirmare pentru modificările distructive (treci argumentul `--force` pentru a sări peste aceasta). | CI, hook-uri pre-commit, agenți AI, fluxuri de lucru scriptate. |
| `yarn twenty plan` | Construiește și afișează modificările de metadate **fără a le aplica**. | Inspectarea modificărilor pe care le-ar face o sincronizare înainte de a le confirma. |
Ambele moduri necesită o conexiune la distanță autentificată. Vezi [Sincronizare și recuperare](/l/ro/developers/extend/apps/operations/sync-and-recovery#previewing-changes-dry-run) pentru mai multe detalii despre `--dry-run`.
Toate modurile necesită o conexiune la distanță autentificată. Vezi [Sincronizare și recuperare](/l/ro/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) pentru mai multe detalii despre `plan`.
<Note>
`yarn twenty dev --once` și `yarn twenty dev --once --dry-run` sunt aliasuri depreciate pentru `yarn twenty apply` și `yarn twenty plan`.
</Note>
### Opțiuni pentru modul de dezvoltare
| Opțiune | Descriere |
| ------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `--once` | Construiește și sincronizează o singură dată, apoi iese. |
| `--dry-run` | Cu `--once`, poți previzualiza modificările de metadate fără a le aplica. Nu scrie nimic. |
| `--debounceMs \<ms>` | Setează întârzierea de debounce pentru modificarea fișierului în milisecunde (implicit: `2000`). |
| `--force` | Aplică modificările distructive (ștergeri) fără confirmare. |
| `--debounceMs \<ms>` | Setează întârzierea de debounce pentru modificarea fișierului în milisecunde (implicit: `1000`). |
| `--verbose` / `--debug` | Afișează jurnale detaliate de construire, cereri de sincronizare și urme ale erorilor. |
## Ce puteți construi
@@ -22,18 +22,22 @@ yarn twenty dev:add frontComponent
## Tipuri de entități disponibile
| Tipul entității | Comandă | Fișier generat |
| ---------------------------- | ---------------------------------------- | ------------------------------------------------------- |
| Obiect | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| Câmp | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| Funcție logică | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| Componentă frontend | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| Rol | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| Abilitate | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| Agent | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| Vizualizare | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Element de meniu de navigare | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Machetă de pagină | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Tipul entității | Comandă | Fișier generat |
| ----------------------------- | ---------------------------------------- | ------------------------------------------------------- |
| Obiect | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| Câmp | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| Funcție logică | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| Componentă frontend | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| Rol | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| Abilitate | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| Agent | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| Vizualizare | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Element de meniu de navigare | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Machetă de pagină | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Fila "Aspect pagină" | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Element din meniul de comenzi | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Câmpul vizualizării | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Furnizor de conexiune | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
## Ce generează scaffolder-ul
@@ -5,10 +5,10 @@ icon: wrench
---
* **Erori Docker** — Asigurați-vă că Docker Desktop (sau daemonul) rulează înainte de `yarn twenty docker:start`. Mesajul de eroare va afișa comanda corectă de pornire pentru sistemul dvs. de operare.
* **Versiune Node greșită** — Aveți nevoie de 24+. Verificați cu `node -v`.
* **Versiune Node greșită** — Este nevoie de 24.5+ (`engines.node: ^24.5.0`). Verificați cu `node -v`.
* **Lipsește Yarn 4** — Rulați `corepack enable`.
* **Dependențe nefuncționale** — `rm -rf node_modules && yarn install`.
* **Erori ale `twenty-sdk` după actualizarea la v2.8.0** — A fost mutat din `dependencies` în `devDependencies` în v2.8.0. Vezi [Structura proiectului → Dependințe](/l/ro/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty build` afișează un avertisment despre `twenty-client-sdk` aflat în `dependencies`** — Este furnizat în timpul execuției de către Twenty, așa că ar trebui mutat în `devDependencies` alături de `twenty-sdk`. Vezi [Structura proiectului → Dependințe](/l/ro/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` afișează un avertisment despre `twenty-client-sdk` aflat în `dependencies`** — Este furnizat în timpul execuției de către Twenty, așa că ar trebui mutat în `devDependencies` alături de `twenty-sdk`. Vezi [Structura proiectului → Dependințe](/l/ro/developers/extend/apps/getting-started/project-structure#dependencies).
Blocat? Întrebați pe [Discordul 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({
## Câmpuri de configurare
| Câmp | Obligatoriu | Descriere |
| --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `universalIdentifier` | Da | ID unic stabil pentru comandă |
| `label` | Da | Etichetă completă afișată în meniul de comenzi (Cmd+K) |
| `frontComponentUniversalIdentifier` | Da | `universalIdentifier` al componentei front-end pe care această comandă o deschide |
| `shortLabel` | Nu | Etichetă mai scurtă afișată pe butonul de acțiune rapidă fixat |
| `icon` | Nu | Numele pictogramei afișat lângă etichetă (de ex. `'IconBolt'`, `'IconSend'`) |
| `isPinned` | Nu | Când este `true`, afișează comanda ca buton de acțiune rapidă în colțul din dreapta sus al paginii |
| `availabilityType` | Nu | Controlează unde apare comanda: `'GLOBAL'` (mereu disponibilă), `'RECORD_SELECTION'` (doar când sunt selectate înregistrări) sau `'FALLBACK'` (afișată când nicio altă comandă nu se potrivește) |
| `availabilityObjectUniversalIdentifier` | Nu | Restricționați comanda la paginile unui anumit tip de obiect (de ex., doar pe înregistrările Company) |
| `conditionalAvailabilityExpression` | Nu | O expresie booleană care controlează dinamic vizibilitatea (vezi mai jos) |
| Câmp | Obligatoriu | Descriere |
| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `universalIdentifier` | Da | ID unic stabil pentru comandă |
| `label` | Da | Etichetă completă afișată în meniul de comenzi (Cmd+K) |
| `frontComponentUniversalIdentifier` | Da | `universalIdentifier` al componentei front-end pe care această comandă o deschide |
| `shortLabel` | Nu | Etichetă mai scurtă afișată pe butonul de acțiune rapidă fixat |
| `icon` | Nu | **Învechit** — ignorat în favoarea pictogramei aplicației; buildul emite un avertisment dacă este setat |
| `isPinned` | Nu | Când este `true`, afișează comanda ca buton de acțiune rapidă în colțul din dreapta sus al paginii |
| `availabilityType` | Nu | Controlează unde apare comanda: `'GLOBAL'` (mereu disponibilă), `'GLOBAL_OBJECT_CONTEXT'` (doar în paginile cu context de obiect — pagini de index și de înregistrare), `'RECORD_SELECTION'` (doar când sunt selectate înregistrări) sau `'FALLBACK'` (afișată când nicio altă comandă nu se potrivește) |
| `availabilityObjectUniversalIdentifier` | Nu | Restricționați comanda la paginile unui anumit tip de obiect (de ex., doar pe înregistrările Company) |
| `conditionalAvailabilityExpression` | Nu | O expresie booleană care controlează dinamic vizibilitatea (vezi mai jos) |
## Comenzi headless
Un element din meniul de comenzi asociat cu un [headless front component](/l/ro/developers/extend/apps/layout/front-components#headless-vs-non-headless) este modalitatea standard de a oferi o acțiune cu un singur clic — de a rula cod, de a naviga sau de a confirma și executa. Pagina Front Components acoperă [SDK Command components](/l/ro/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) care gestionează modelul acțiune-și-demontare.
Un flux tipic:
```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,
});
```
Un flux tipic: un component headless redă `<Command execute={...} />` (vezi [exemplul complet](/l/ro/developers/extend/apps/layout/front-components#sdk-command-components)), iar elementul de meniu de comandă îl indică:
```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',
});
```
După sincronizarea cu `yarn twenty dev` (sau prin rularea comenzii `yarn twenty dev --once` o singură dată), acțiunea rapidă apare în colțul din dreapta sus al paginii:
După sincronizarea cu `yarn twenty dev` (sau prin rularea comenzii unice `yarn twenty apply`), acțiunea rapidă apare în colțul din dreapta sus al paginii:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Buton de acțiune rapidă în colțul din dreapta sus" />
@@ -88,11 +87,11 @@ Componentele front-end au două moduri de randare controlate de opțiunea `isHea
```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 @@ Deoarece componenta returnează `null`, Twenty omite redarea unui container pent
Pachetul `twenty-sdk` oferă patru componente ajutătoare Command, concepute pentru componente front-end headless. Fiecare componentă execută o acțiune la montare, gestionează erorile afișând o notificare snackbar și demontează automat componenta front-end la final.
Importați-le din `twenty-sdk/command`:
Importați-le din `twenty-sdk/front-component`:
* **`Command`** — Rulează un callback asincron prin prop-ul `execute`.
* **`CommandLink`** — Navighează către o rută a aplicației. Props: `to`, `params`, `queryParams`, `options`.
@@ -127,8 +126,8 @@ Iată un exemplu complet de componentă front-end headless care folosește `Comm
```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 @@ export default defineCommandMenuItem({
```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 @@ Componentele de front rulează în browser într-un Web Worker izolat, în timp
O funcție logică declarată cu `httpRouteTriggerSettings` este accesibilă prin HTTP la ruta sa. Twenty injectează în worker URL-ul de bază de la care sunt deservite funcțiile tale ca `TWENTY_FUNCTIONS_URL`, împreună cu `TWENTY_APP_ACCESS_TOKEN` care autentifică apelul. Nu există încă un client SDK dedicat pentru apelarea propriilor funcții, așa că apelează-le cu un simplu `fetch`:
> **În Twenty Cloud, funcțiile logice declanșate prin HTTP sunt deservite pe un domeniu dedicat pentru fiecare spațiu de lucru** la `https://\<your-workspace-subdomain>.twenty.com\<path>` — acesta este exact URL-ul la care indică `TWENTY_FUNCTIONS_URL`. Pentru apelanții externi, copiază URL-ul exact din setările **HTTP trigger** ale funcției sau din fila **Settings** a aplicației.
> **În Twenty Cloud, funcțiile logice declanșate prin HTTP sunt deservite pe un domeniu dedicat pentru fiecare spațiu de lucru** la `https://\<your-workspace-subdomain>.withtwenty.com\<path>` — acesta este exact URL-ul la care indică `TWENTY_FUNCTIONS_URL`. Pentru apelanții externi, copiază URL-ul exact din setările **HTTP trigger** ale funcției sau din fila **Settings** a aplicației.
<Warning>
Ruta veche a funcției `/s/` este **depășită** și va fi **dezactivată la 2026-07-24**. Folosește în schimb `TWENTY_FUNCTIONS_URL` (mai sus) și migrează orice URL-uri `/s/` hard-codate înainte de acea dată. Ruta `/s/` rămâne disponibilă pentru self-hosting.
@@ -212,7 +210,7 @@ O componentă de front headless poate efectua apelul la montare prin componenta
```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 @@ try {
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 @@ Iată un exemplu care folosește API-ul gazdei pentru a afișa un snackbar și a
```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({
Folosiți `useSelectedRecordIds()` pentru a gestiona mai multe înregistrări selectate. Acest lucru este util pentru operațiuni în masă:
```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,
},
});
```
Afișați-o cu un [element de meniu de comandă](/l/ro/developers/extend/apps/layout/command-menu-items) restricționat la selecțiile de înregistrări:
```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` controlează ordonarea în bara laterală.
* Enumul conține și `NavigationMenuItemType.RECORD`, utilizat intern pentru favoritele de înregistrări create de utilizator — nu poate fi folosit dintr-un manifest de aplicație (nu există niciun câmp pentru a face referire la o înregistrare).
* `icon` și `color` sunt opționale și personalizează aspectul intrării.
* `folderUniversalIdentifier` este de asemenea disponibil pe orice element pentru a-l îmbrica într-un părinte de tip `FOLDER`.
@@ -33,17 +33,32 @@ export default defineView({
## Puncte cheie
* `objectUniversalIdentifier` specifică la ce obiect se aplică această vizualizare. Poate fi un obiect personalizat pe care l-ați definit sau un obiect standard Twenty.
* `key` determină tipul vizualizării — `ViewKey.INDEX` este principala vizualizare de listă pentru obiect.
* `key: ViewKey.INDEX` marchează vizualizarea ca vizualizarea principală de listă a obiectului (cea pe care o deschide un element de navigare `OBJECT`).
* `fields` controlează ce coloane apar și ordinea acestora. Fiecare câmp face referire la un `fieldMetadataUniversalIdentifier`.
* Puteți declara, de asemenea, `filters`, `filterGroups`, `groups` și `fieldGroups` pentru configurații avansate.
* Puteți declara, de asemenea, `filters`, `filterGroups`, `sorts`, `groups` și `fieldGroups` pentru configurații avansate.
* `position` controlează ordonarea atunci când există mai multe vizualizări pentru același obiect.
## Proprietăți opționale
| Proprietate | Valori | Descriere |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | `ViewType.TABLE` (implicit), `ViewType.KANBAN`, `ViewType.CALENDAR` | Modul în care sunt dispuse înregistrările. (`FIELDS_WIDGET` / `TABLE_WIDGET` există, de asemenea, dar sunt folosite intern de către widget-urile de tip page-layout.) |
| `visibility` | `ViewVisibility.WORKSPACE` (implicit), `ViewVisibility.UNLISTED` | Dacă vizualizarea este listată pentru întregul spațiu de lucru sau ascunsă din selectoare. |
| `openRecordIn` | `ViewOpenRecordIn.SIDE_PANEL` (implicit), `ViewOpenRecordIn.RECORD_PAGE` | Unde se deschide o înregistrare la clic. |
| `sorts` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Ordinea implicită de sortare. |
| `isCompact` | `boolean` | Afișare compactă a rândurilor. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Gruparea înregistrărilor (de ex. coloane kanban) după un câmp. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Agregări și dimensionare pentru coloanele kanban. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Vizualizări de tip calendar: layout și câmpul de dată care poziționează înregistrările. |
Toate enum-urile de mai sus sunt exportate din `twenty-sdk/define`.
## Filtre
O vizualizare poate include filtre aplicate în prealabil. Fiecare filtru are trei coordonate: **câmpul** care este filtrat, **operandul** (cum se compară) și **valoarea** (față de ce se compară). Toate cele trei trebuie să se potrivească — folosirea unui operand care nu se aplică unui tip de câmp va fi respinsă în timpul sincronizării.
```ts
import { ViewFilterOperand } from 'twenty-shared/types';
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
@@ -51,8 +51,12 @@ export default defineLogicFunction({
```
Tipuri de declanșatoare disponibile:
* **httpRoute**: Expune funcția pe o cale și metodă HTTP **sub endpoint-ul `/s/`**:
> de ex. `path: '/post-card/create'` este apelabil la `https://your-twenty-server.com/s/post-card/create`
* **httpRoute**: Expune funcţia pe o cale HTTP şi pe o metodă în **funcţiunea URL-ul bazei de lucru** - valoarea de douăzeci de injectări ca `TWENTY_FUNCTIONS_URL` (pe douăzeci Cloud, un domeniu specializat pentru spațiul de lucru):
> de ex. `path: '/post-card/create'` este apelabil la `https://your-workspace.withtwenty.com/post-card/create`
<Warning>
Prefixul moștenirii `/s/` (`https://your-twenty-server.com/s/post-card/create`) este **învechit pe 20 de Cloud** și va fi dezactivat pe **2026-07-24**. Rămâne disponibil pentru instanțe auto-găzduite și locale care nu configurează un domeniu de funcții izolate - utilizați `TWENTY_FUNCTIONS_URL` când este setat, şi întoarceţi-vă la `\<server-url>/s/\<path>` altfel.
</Warning>
<Note>
Pentru a apela o funcție logică declanșată de o rută dintr-o componentă front-end (headless), consultă [Apelarea unei funcții logice](/l/ro/developers/extend/apps/layout/front-components#calling-a-logic-function).
@@ -42,7 +42,7 @@ O funcție de logică alege unul sau mai multe declanșatoare — fiecare intrar
| Declanșator | Când rulează | Setare |
| ----------------------------- | ----------------------------------------------------------------- | ------------------------------- |
| **Rută HTTP** | O cerere ajunge la endpointul tău `/s/\<path>` | `httpRouteTriggerSettings` |
| **Rută HTTP** | O solicitare accesează URL-ul public al funcției tale | `httpRouteTriggerSettings` |
| **Cron** | O expresie CRON se potrivește | `cronTriggerSettings` |
| **Eveniment de bază de date** | O înregistrare din workspace este creată, actualizată sau ștearsă | `databaseEventTriggerSettings` |
| **Instrument IA** | O funcționalitate IA din Twenty decide să apeleze funcția ta | `toolTriggerSettings` |
@@ -4,7 +4,25 @@ description: comenzi `yarn twenty` pentru executarea funcțiilor, transmiterea
icon: terminal
---
Dincolo de `dev`, `dev:build`, `dev:add` și `dev:typecheck`, `yarn twenty` CLI oferă comenzi pentru executarea funcțiilor, vizualizarea jurnalelor și gestionarea instalărilor de aplicații.
Interfața CLI `yarn twenty` este punctul tău de acces pentru tot ce ține de aplicație. Lista completă de comenzi:
| Comandă | Ce face | Documentat în |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `dev` | Monitorizează fișierele sursă și sincronizează în timp real modificările | [Ghid de pornire rapidă](/l/ro/developers/extend/apps/getting-started/quick-start) |
| `plan` | Previzualizează modificările metadatelor fără a le aplica | [Sincronizare și recuperare](/l/ro/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `aplică` | Aplică modificările metadatelor după afișarea planului | [Sincronizare și recuperare](/l/ro/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Compilează aplicația și generează clientul API (`--tarball` pentru a împacheta un `.tgz`) | [Publicare](/l/ro/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Rulează verificarea tipurilor TypeScript | [Testare](/l/ro/developers/extend/apps/operations/testing) |
| `dev:add` | Creează scheletul unei entități noi | [Generarea scheletului](/l/ro/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Regenerează clientul API tipizat | această pagină |
| `dev:function:exec` / `dev:function:logs` | Execută funcții și transmite în flux jurnalele acestora | această pagină |
| `dev:translations-extract` | Extrage șirurile traducibile în cataloagele din `locales/` | [Traduceri](/l/ro/developers/extend/apps/translations/overview) |
| `dev:catalog-sync` | Declanșează o sincronizare a catalogului marketplace-ului | [Publicare](/l/ro/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Ciclul de viață al versiunilor | [Publicare](/l/ro/developers/extend/apps/operations/publishing) și această pagină |
| `docker:*` | Administrează containerul serverului Twenty local | [Server local](/l/ro/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Administrează conexiunile la server | această pagină |
Fiecare comandă acceptă `-r, --remote \<name>` pentru a viza un anumit server la distanță în locul celui implicit.
## Executarea funcțiilor (`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
```
## Vizualizarea jurnalelor funcțiilor (`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>
```
Acreditările dvs. sunt stocate în `~/.twenty/config.json`.
@@ -229,7 +229,7 @@ yarn twenty dev:catalog-sync
# yarn twenty dev:catalog-sync --remote production
```
Metadatele afișate în marketplace provin din configurația `defineApplication()` — câmpuri precum `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` și `termsUrl`.
Metadatele afișate în marketplace provin din configurația `defineApplication()` — vezi secțiunea [Metadate pentru marketplace](#marketplace-metadata) de mai sus.
<Note>
Dacă aplicația ta nu definește un `aboutDescription` în `defineApplication()`, piața va folosi automat fișierul `README.md` al pachetului tău de pe npm drept conținut pentru pagina Despre. Acest lucru înseamnă că poți menține un singur README atât pentru npm, cât și pentru piața Twenty. Dacă vrei o descriere diferită în piață, setează explicit `aboutDescription`.
@@ -15,33 +15,44 @@ Pentru iterațiile locale de zi cu zi vei dori aproape întotdeauna `yarn twenty
| Vrei să… | Comandă | Notițe |
| -------------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Iterează local cu sincronizare în timp real | `yarn twenty dev` | Monitorizează fișierele și sincronizează la fiecare modificare. |
| Sincronizează o singură dată și iese (CI, scripturi, hook-uri) | `yarn twenty dev --once` | O singură compilare + sincronizare, apoi iese. |
| Previzualizează modificările **fără a le aplica** | `yarn twenty dev --once --dry-run` | Calculează și afișează diff-ul; nu scrie nimic. |
| Sincronizează o singură dată și iese (CI, scripturi, hook-uri) | `yarn twenty apply` | O singură compilare + sincronizare, apoi iese. Adaugă `--force` pentru a omite confirmarea schimbărilor distructive. |
| Previzualizează modificările **fără a le aplica** | `yarn twenty plan` | Calculează și afișează diff-ul; nu scrie nimic. |
| Elimină aplicația din spațiul de lucru | `yarn twenty app:uninstall` | Adaugă `--yes` pentru a sări peste prompt. |
| Trimite un tarball către un server | `yarn twenty app:publish --private` | Necesită o versiune `package.json` **strict mai mare** — vezi [Publicare](/l/ro/developers/extend/apps/operations/publishing). |
| Publică în marketplace (npm) | `yarn twenty app:publish` | — |
| Instalează / actualizează o versiune deja implementată | `yarn twenty app:install` | Instalează versiunea implementată în prezent. |
| Șterge serverul local și pornește de la zero | `yarn twenty docker:reset` | Șterge **toate** datele locale — ultimă soluție. |
<Note>
`yarn twenty dev --once` și `yarn twenty dev --once --dry-run` funcționează în continuare ca aliasuri depreciate pentru `yarn twenty apply` și `yarn twenty plan`.
</Note>
### Sincronizarea locală nu are nevoie de incrementarea versiunii
Regula de `version` strict crescătoare (`VERSION_ALREADY_EXISTS` la deploy, `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` la instalare) se aplică pentru **`app:publish` / `app:install`** — calea de release. `yarn twenty dev` sincronizează manifestul pe loc și nu necesită niciodată schimbarea versiunii, astfel încât nu trebuie să atingi `package.json` pentru a itera. Dacă ajungi să crești versiunea ca să testezi o modificare locală, folosești calea de release atunci când ai nevoie de bucla de dezvoltare.
## Citirea rezultatului sincronizării
Fiecare sincronizare afișează modificările de metadate pe care le-a aplicat (sau le-ar aplica, cu `--dry-run`):
Fiecare sincronizare afișează modificările de metadate pe care le-a aplicat (sau le-ar aplica, cu `plan`), în stil Terraform — un bloc per entitate cu atributele sale, apoi o linie de rezumat:
```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)
```
Acesta este primul tău instrument de diagnostic: îți spune exact ce obiecte, câmpuri și layout-uri s-au schimbat, astfel încât să poți confirma că o sincronizare a făcut ce te așteptai înainte să verifici interfața.
Schimbările distructive (`to destroy`) sunt listate împreună cu ceea ce elimină (de ex. `objectMetadata "auditNote" — drops the table and all its rows`) și necesită confirmare interactivă sau `--force` în scripturi.
Când o sincronizare eșuează pe o singură entitate, eroarea numește entitatea problematică și `universalIdentifier`-ul acesteia, de exemplu:
```text
@@ -50,39 +61,42 @@ Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337)
Folosește acel identificator pentru a găsi entitatea în manifest (și, dacă este nevoie, în spațiul de lucru) în loc să ghicești care intră în conflict.
## Previzualizarea modificărilor (dry run)
## Previzualizarea modificărilor (plan)
`yarn twenty dev --once --dry-run` construiește manifestul, cere serverului planul de migrare și îl afișează — **fără a aplica nimic**. Este modalitatea sigură de a răspunde la întrebarea „ce ar schimba această sincronizare?” înainte de a te angaja la ea.
`yarn twenty plan` construiește manifestul, cere serverului planul de migrare și îl afișează — **fără a aplica nimic**. Este modalitatea sigură de a răspunde la întrebarea „ce ar schimba această sincronizare?” înainte de a te angaja la ea.
```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
```
Un dry run:
Un plan:
* **Nu scrie nimic** — fără migrare de metadate, fără actualizare a înregistrării aplicației, fără modificări ale rolului sau filei implicite și fără generare de client API.
* Returnează **același diff** pe care l-ar aplica o sincronizare reală, astfel încât poți revizui dinainte entitățile create/actualizate/șterse.
* Este util înaintea unei modificări riscante, când revizuiești o modificare generată de AI sau într-un script care ar trebui să eșueze dacă o modificare neașteptată este pe cale să fie aplicată.
<Note>
Un dry run previzualizează doar modificările de **metadate** și necesită ca aplicația să fi fost sincronizată cel puțin o dată (astfel încât spațiul de lucru să știe de ea). Dacă îl rulezi pentru o aplicație care nu a fost niciodată sincronizată, serverul va raporta că aplicația nu este instalată — rulează mai întâi o dată `yarn twenty dev`.
Un plan previzualizează doar modificările de **metadate** și necesită ca aplicația să fi fost sincronizată cel puțin o dată (astfel încât spațiul de lucru să știe de ea). Dacă îl rulezi pentru o aplicație care nu a fost niciodată sincronizată, serverul va raporta că aplicația nu este instalată — rulează mai întâi o dată `yarn twenty dev`.
</Note>
## Plan de recuperare în trepte
Când metadatele locale par greșite, escaladează în această ordine și oprește-te de îndată ce ești deblocat. Fiecare pas este mai disruptiv decât precedentul.
1. **Resincronizează.** Rulează din nou `yarn twenty dev --once`. Sincronizările sunt idempotente — rularea din nou a unui manifest curat este sigură și rezolvă adesea o problemă temporară.
2. **Previzualizează planul.** Rulează `yarn twenty dev --once --dry-run` pentru a vedea exact ce intenționează să schimbe următoarea sincronizare, fără a o aplica.
1. **Resincronizează.** Rulează din nou `yarn twenty apply`. Sincronizările sunt idempotente — rularea din nou a unui manifest curat este sigură și rezolvă adesea o problemă temporară.
2. **Previzualizează planul.** Rulează `yarn twenty plan` pentru a vedea exact ce intenționează să schimbe următoarea sincronizare, fără a o aplica.
3. **Citește eroarea nominalizată.** Dacă o sincronizare eșuează, notează tipul de metadate și `universalIdentifier`-ul din mesaj (vezi mai sus) și localizează acea entitate în manifest. Un conflict indică de obicei un identificator duplicat sau reutilizat.
4. **Dezinstalează și reinstalează.** `yarn twenty app:uninstall`, apoi sincronizează din nou (`yarn twenty dev`). Acest lucru reconstruiește metadatele aplicației de la zero, păstrând în același timp restul spațiului de lucru intact.
5. **Resetare completă (ultimă soluție).** `yarn twenty docker:reset`, apoi reinițializează datele și resincronizează.
@@ -78,6 +78,13 @@ Creați un `vitest.config.ts` în rădăcina aplicației:
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,
},
},
});
```
Creați un fișier de configurare care verifică faptul că serverul este accesibil înainte de rularea testelor:
Creează un fișier global de configurare inițială care verifică faptul că serverul este accesibil, scrie un fișier de configurare de test pentru SDK (`~/.twenty/config.test.json`) și sincronizează aplicația înainte ca testele să ruleze:
```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 });
}
```
## API-uri SDK programatice
Subcalea `twenty-sdk/cli` exportă funcții pe care le puteți apela direct din codul de test:
| Funcție | Descriere |
| -------------- | --------------------------------------------------------- |
| `appBuild` | Construiți aplicația și, opțional, împachetați un tarball |
| `appDeploy` | Încărcați un tarball pe server |
| `appInstall` | Instalați aplicația în spațiul de lucru activ |
| `appUninstall` | Dezinstalați aplicația din spațiul de lucru activ |
| Funcție | Descriere |
| -------------- | -------------------------------------------------------------------------------------- |
| `appBuild` | Construiți aplicația și, opțional, împachetați un tarball |
| `appDeploy` | Încărcați un tarball pe server |
| `appDevOnce` | Construiește și sincronizează aplicația o singură dată (la fel ca `yarn twenty apply`) |
| `appInstall` | Instalați aplicația în spațiul de lucru activ |
| `appUninstall` | Dezinstalați aplicația din spațiul de lucru activ |
Fiecare funcție returnează un obiect rezultat cu `success: boolean` și fie `data`, fie `error`.
@@ -238,64 +253,10 @@ Puteți rula și verificarea tipurilor pe aplicație fără a rula testele:
yarn twenty dev:typecheck
```
Aceasta rulează `tsc --noEmit` și raportează orice erori de tip.
Aceasta rulează `tsc --noEmit` împotriva fișierului `tsconfig.json` al aplicației și raportează orice erori de tip. Aplicațiile generate cu scaffold includ, de asemenea, un script `yarn typecheck` care acoperă și fișierele de test (`tsconfig.spec.json`).
## CI cu GitHub Actions
Scaffolderul generează un workflow GitHub Actions gata de utilizare în `.github/workflows/ci.yml`. Rulează automat testele de integrare la fiecare push pe `main` și la pull request-uri.
Scaffolderul generează un workflow gata de utilizare la `.github/workflows/ci.yml`. La fiecare push pe `main` și la fiecare pull request, acesta pornește un server Twenty efemer în runner (prin acțiunea `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`), apoi rulează `yarn lint`, `yarn typecheck`, `yarn test:unit` și `yarn test` cu `TWENTY_API_URL` / `TWENTY_API_KEY` îndreptate către acel server. Nu sunt necesare secrete și poți fixa versiunea serverului prin variabila de mediu `TWENTY_VERSION` din partea de sus a workflow-ului.
Workflow-ul:
1. Preia codul
2. Pornește un server Twenty temporar folosind acțiunea `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Instalează dependențele cu `yarn install --immutable`
4. Rulează `yarn test` cu `TWENTY_API_URL` și `TWENTY_API_KEY` injectate din rezultatele acțiunii
```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 }}
```
Nu trebuie să configurați niciun secret — acțiunea `spawn-twenty-docker-image` pornește un server Twenty efemer direct în runner și oferă detaliile de conectare. Secretul `GITHUB_TOKEN` este furnizat automat de GitHub.
Pentru a fixa o versiune Twenty specifică în loc de `latest`, modificați variabila de mediu `TWENTY_VERSION` din partea de sus a workflow-ului.
Vezi [Publicare → CI/CD automatizat](/l/ro/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) pentru un ghid complet al ambelor workflow-uri generate cu scaffold (`ci.yml` și pipeline-ul 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 @@ Același gestionar poate răspunde și la solicitările HTTP. Vom adăuga două
* a **POST** final apeluri interfață pentru a genera un document, și
* un obiectiv public **GET** care face un document ca o pagină web printabilă.
Ambele folosesc `httpRouteTriggerSettings`. Rutele aplicațiilor sunt servite sub `/s` pe
Douăzeci de servere (ex. `http://localhost:2020/s/documents/generate`).
Ambele folosesc `httpRouteTriggerSettings`. Pe server-ul local dev, rutele aplicației sunt servite
sub prefixul `/s` (ex. `http://localhost:2020/s/documents/generate`).
<Note>
În 22 de Cloud, rutele sunt servite pe domeniul funcțiilor dedicate din spațiul de lucru
— URL-ul Douăzeci injectează ca `TWENTY_FUNCTIONS_URL`, fără prefixul `/s`. Prefixul
este învechit acolo şi rămâne doar pentru instanţele auto-găzduite şi locale.
Vedeți [Apelarea unei funcții logice](/l/ro/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
## Ruta POST generarea la cerere
@@ -76,11 +76,11 @@ Rulează aceleași porți CI face:
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
```
Derularea uscată tipărește exact ce s-ar schimba pe server fără a o aplica —
un bun control sanitar final. Vezi
Planul afișează exact ce s-ar schimba pe server, fără a aplica modificările
o bună verificare finală. Vezi
[Testing](/l/ro/developers/extend/apps/operations/testing) şi
[Sincronizare şi Recuperare](/l/ro/developers/extend/apps/operations/sync-and-recovery).