Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### كيف يعمل التجميع - -تستخدم خطوة البناء أداة esbuild لإنتاج ملف واحد مستقل لكل دالة منطقية ولكل مكوّن أمامي. تُضمَّن جميع الحزم المستوردة داخل الحزمة. - -**الدوال المنطقية** تعمل في بيئة Node.js. الوحدات المدمجة في Node (`fs` و`path` و`crypto` و`http` وغيرها) متاحة ولا تحتاج إلى تثبيت. - -**المكوّنات الأمامية** تعمل ضمن Web Worker. وحدات Node المدمجة غير متاحة — المتاح فقط واجهات برمجة المتصفّح وحِزَم npm التي تعمل في بيئة المتصفّح. - -كلتا البيئتين تحتويان على `twenty-client-sdk/core` و`twenty-client-sdk/metadata` كوحدات متاحة مُسبقًا — لا تُضمَّن هذه ضمن الحزم بل تُحلّ وقت التشغيل بواسطة الخادم. - -## اختبار تطبيقك - -يوفّر SDK واجهات برمجة قابلة للتنفيذ برمجيًا تمكّنك من بناء تطبيقك ونشره وتثبيته وإلغاء تثبيته من شيفرة الاختبار. بالاقتران مع [Vitest](https://vitest.dev/) وعملاء واجهة البرمجة مضبوطي الأنواع، يمكنك كتابة اختبارات تكامل تتحقّق من أن تطبيقك يعمل من البداية إلى النهاية مقابل خادم Twenty حقيقي. - -### إعداد - -يتضمّن التطبيق المُولَّد بالقالب بالفعل Vitest. إذا أعددته يدويًا، فثبّت التبعيات: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -أنشئ `vitest.config.ts` في جذر تطبيقك: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -أنشئ ملف إعداد يتحقّق من إمكانية الوصول إلى الخادم قبل تشغيل الاختبارات: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### واجهات SDK البرمجية - -يُصدِّر المسار الفرعي `twenty-sdk/cli` دوالًا يمكنك استدعاؤها مباشرةً من شيفرة الاختبار: - -| دالة | الوصف | -| -------------- | ----------------------------------------- | -| `appBuild` | بناء التطبيق واختياريًا حزم ملف tarball | -| `appDeploy` | رفع ملف tarball إلى الخادم | -| `appInstall` | تثبيت التطبيق على مساحة العمل النشطة | -| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة | - -تُرجع كل دالة كائن نتيجة يحتوي على `success: boolean` وعلى إمّا `data` أو `error`. - -### كتابة اختبار تكامل - -إليك مثالًا كاملًا يبني التطبيق وينشره ويثبّته، ثم يتحقّق من ظهوره في مساحة العمل: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### تشغيل الاختبارات - -تأكّد من تشغيل خادم Twenty المحلي لديك، ثم: - -```bash filename="Terminal" -yarn test -``` - -أو في وضع المراقبة أثناء التطوير: - -```bash filename="Terminal" -yarn test:watch -``` - -### التحقق من الأنواع - -يمكنك أيضًا تشغيل التحقق من الأنواع على تطبيقك دون تشغيل الاختبارات: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -يشغِّل هذا الأمر `tsc --noEmit` ويبلغ عن أي أخطاء في الأنواع. - -## مرجع CLI - -بالإضافة إلى `dev` و`build` و`add` و`typecheck`، يوفّر CLI أوامر لتنفيذ الدوال وعرض السجلات وإدارة تثبيتات التطبيقات. - -### تنفيذ الدوال (`yarn twenty exec`) - -تشغيل دالة منطقية يدويًا دون تشغيلها عبر HTTP أو cron أو حدث قاعدة بيانات: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### عرض سجلات الدوال (`yarn twenty logs`) - -بثّ سجلات التنفيذ لدوال تطبيقك المنطقية: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-راجع [قسم الأصول العامة](/l/ar/developers/extend/apps/cli-and-testing#public-assets-public-folder) للتفاصيل.
-
-## التنسيق
-
-تدعم المكوّنات الأمامية عدة أساليب للتنسيق. يمكنك استخدام:
-
-* **أنماط مضمنة** — `style={{ color: 'red' }}`
-* **مكوّنات Twenty لواجهة المستخدم** — استورد من `twenty-sdk/ui` (Button وTag وStatus وChip وAvatar وغيرها)
-* **Emotion** — CSS-in-JS مع `@emotion/react`
-* **Styled-components** — أنماط `styled.div`
-* **Tailwind CSS** — أصناف مساعدة
-* **أي مكتبة CSS-in-JS** متوافقة مع React
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Jak funguje bundlování - -Krok sestavení používá esbuild k vytvoření jediného samostatného souboru pro každou logickou funkci a každou frontendovou komponentu. Všechny importované balíčky jsou vloženy přímo do bundlu. - -**Logické funkce** běží v prostředí Node.js. Vestavěné moduly Node (`fs`, `path`, `crypto`, `http` atd.) jsou k dispozici a není je třeba instalovat. - -**Frontendové komponenty** běží ve Web Workeru. Vestavěné moduly Node nejsou k dispozici — pouze prohlížečová API a balíčky npm, které fungují v prohlížečovém prostředí. - -V obou prostředích jsou jako předpřipravené moduly k dispozici `twenty-client-sdk/core` a `twenty-client-sdk/metadata` — nejsou součástí bundlu, ale server je za běhu načítá. - -## Testování vaší aplikace - -SDK poskytuje programová rozhraní, která vám umožní z testovacího kódu aplikaci sestavit, nasadit, nainstalovat a odinstalovat. V kombinaci s [Vitest](https://vitest.dev/) a typovanými klienty API můžete psát integrační testy, které ověří, že vaše aplikace funguje end-to-end proti reálnému serveru Twenty. - -### Nastavení - -Vygenerovaná aplikace již obsahuje Vitest. Pokud to nastavujete ručně, nainstalujte závislosti: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Vytvořte `vitest.config.ts` v kořeni vaší aplikace: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Vytvořte soubor nastavení, který před spuštěním testů ověří dostupnost serveru: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Programová rozhraní SDK - -Subcesta `twenty-sdk/cli` exportuje funkce, které můžete volat přímo z testovacího kódu: - -| Funkce | Popis | -| -------------- | ----------------------------------------------------- | -| `appBuild` | Sestaví aplikaci a volitelně zabalí tarball | -| `appDeploy` | Nahraje tarball na server | -| `appInstall` | Nainstaluje aplikaci do aktivního pracovního prostoru | -| `appUninstall` | Odinstaluje aplikaci z aktivního pracovního prostoru | - -Každá funkce vrací objekt výsledku se `success: boolean` a buď `data`, nebo `error`. - -### Psání integračního testu - -Zde je kompletní příklad, který aplikaci sestaví, nasadí a nainstaluje a poté ověří, že se objeví v pracovním prostoru: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Spuštění testů - -Ujistěte se, že běží váš lokální server Twenty, a poté: - -```bash filename="Terminal" -yarn test -``` - -Nebo v režimu watch během vývoje: - -```bash filename="Terminal" -yarn test:watch -``` - -### Kontrola typů - -Kontrolu typů můžete spustit i na vaší aplikaci bez spuštění testů: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Spustí se `tsc --noEmit` a nahlásí se případné chyby typů. - -## Referenční dokumentace CLI - -Kromě `dev`, `build`, `add` a `typecheck` poskytuje CLI příkazy pro spouštění funkcí, zobrazení logů a správu instalací aplikací. - -### Spouštění funkcí (`yarn twenty exec`) - -Spusťte logickou funkci ručně bez vyvolání přes HTTP, cron nebo databázovou událost: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Zobrazení logů funkcí (`yarn twenty logs`) - -Streamujte výstupní logy běhu logických funkcí vaší aplikace: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-Podrobnosti viz [sekci veřejných souborů](/l/cs/developers/extend/apps/cli-and-testing#public-assets-public-folder).
-
-## Styling
-
-Frontendové komponenty podporují více přístupů ke stylování. Můžete použít:
-
-* **Inline styly** — `style={{ color: 'red' }}`
-* **Komponenty Twenty UI** — import z `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar a další)
-* **Emotion** — CSS-in-JS s `@emotion/react`
-* **Styled-components** — vzory `styled.div`
-* **Tailwind CSS** — utilitní třídy
-* **Jakákoli CSS-in-JS knihovna** kompatibilní s Reactem
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Wie das Bundling funktioniert - -Der Build-Schritt verwendet esbuild, um pro Logikfunktion und pro Frontend-Komponente eine einzelne, in sich geschlossene Datei zu erzeugen. Alle importierten Pakete werden in das Bundle eingebettet. - -**Logikfunktionen** laufen in einer Node.js-Umgebung. Eingebaute Node.js-Module (`fs`, `path`, `crypto`, `http` usw.) stehen zur Verfügung und müssen nicht installiert werden. - -**Frontend-Komponenten** laufen in einem Web Worker. Eingebaute Node.js-Module sind **nicht** verfügbar — nur Browser-APIs und npm-Pakete, die in einer Browserumgebung funktionieren. - -In beiden Umgebungen stehen `twenty-client-sdk/core` und `twenty-client-sdk/metadata` als vorab bereitgestellte Module zur Verfügung — sie werden nicht gebündelt, sondern zur Laufzeit vom Server aufgelöst. - -## Ihre App testen - -Das SDK stellt programmgesteuerte APIs bereit, mit denen Sie Ihre App aus Testcode heraus bauen, bereitstellen, installieren und deinstallieren können. In Kombination mit [Vitest](https://vitest.dev/) und den typisierten API-Clients können Sie Integrationstests schreiben, die prüfen, dass Ihre App End-to-End gegen einen echten Twenty-Server funktioniert. - -### Einrichtung - -Die erzeugte App enthält bereits Vitest. Wenn Sie es manuell einrichten, installieren Sie die Abhängigkeiten: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Erstellen Sie eine `vitest.config.ts` im Stammverzeichnis Ihrer App: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Erstellen Sie eine Setup-Datei, die vor dem Testlauf überprüft, dass der Server erreichbar ist: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Programmgesteuerte SDK-APIs - -Der Subpfad `twenty-sdk/cli` exportiert Funktionen, die Sie direkt aus Testcode aufrufen können: - -| Funktion | Beschreibung | -| -------------- | ----------------------------------------------------- | -| `appBuild` | Die App bauen und optional ein Tarball packen | -| `appDeploy` | Ein Tarball auf den Server hochladen | -| `appInstall` | Die App im aktiven Arbeitsbereich installieren | -| `appUninstall` | Die App aus dem aktiven Arbeitsbereich deinstallieren | - -Jede Funktion gibt ein Ergebnisobjekt mit `success: boolean` und entweder `data` oder `error` zurück. - -### Einen Integrationstest schreiben - -Hier ist ein vollständiges Beispiel, das die App baut, bereitstellt und installiert und anschließend prüft, dass sie im Arbeitsbereich erscheint: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Tests ausführen - -Stellen Sie sicher, dass Ihr lokaler Twenty-Server läuft, und führen Sie dann Folgendes aus: - -```bash filename="Terminal" -yarn test -``` - -Oder im Watch-Modus während der Entwicklung: - -```bash filename="Terminal" -yarn test:watch -``` - -### Typprüfung - -Sie können die Typprüfung Ihrer App auch ohne Tests ausführen: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Dies führt `tsc --noEmit` aus und meldet etwaige Typfehler. - -## CLI-Referenz - -Zusätzlich zu `dev`, `build`, `add` und `typecheck` bietet die CLI Befehle zum Ausführen von Funktionen, Anzeigen von Logs und Verwalten von App-Installationen. - -### Funktionen ausführen (`yarn twenty exec`) - -Eine Logikfunktion manuell ausführen, ohne sie über HTTP, Cron oder ein Datenbankereignis auszulösen: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Funktionsprotokolle ansehen (`yarn twenty logs`) - -Ausführungsprotokolle für die Logikfunktionen Ihrer App streamen: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -Export {selectedRecordIds.length} selected record(s)?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-Details finden Sie im Abschnitt [Öffentliche Assets](/l/de/developers/extend/apps/cli-and-testing#public-assets-public-folder).
-
-## Styling
-
-Front-Komponenten unterstützen mehrere Styling-Ansätze. Sie können verwenden:
-
-* **Inline-Styles** — `style={{ color: 'red' }}`
-* **Twenty-UI-Komponenten** — Import aus `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar und mehr)
-* **Emotion** — CSS-in-JS mit `@emotion/react`
-* **Styled-components** — `styled.div`-Muster
-* **Tailwind CSS** — Utility-Klassen
-* **Beliebige CSS-in-JS-Bibliothek**, die mit React kompatibel ist
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Come funziona il bundling - -La fase di build usa esbuild per produrre un singolo file autonomo per ogni funzione logica e per ogni componente front-end. Tutti i pacchetti importati sono incorporati nel bundle. - -**Le funzioni logiche** vengono eseguite in un ambiente Node.js. I moduli integrati di Node (`fs`, `path`, `crypto`, `http`, ecc.) sono disponibili e non necessitano di essere installati. - -**I componenti front-end** vengono eseguiti in un Web Worker. I moduli integrati di Node non sono disponibili — solo le API del browser e i pacchetti npm che funzionano in un ambiente browser. - -Entrambi gli ambienti hanno `twenty-client-sdk/core` e `twenty-client-sdk/metadata` disponibili come moduli preforniti — questi non vengono inclusi nel bundle ma vengono risolti a runtime dal server. - -## Testare la tua app - -L'SDK fornisce API programmatiche che ti consentono di compilare, distribuire, installare e disinstallare la tua app dal codice di test. In combinazione con [Vitest](https://vitest.dev/) e i client API tipizzati, puoi scrivere test di integrazione che verificano che la tua app funzioni end-to-end contro un server Twenty reale. - -### Impostazione - -L'app generata tramite scaffolding include già Vitest. Se la configuri manualmente, installa le dipendenze: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Crea un `vitest.config.ts` alla radice della tua app: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Crea un file di setup che verifichi che il server sia raggiungibile prima dell'esecuzione dei test: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### API programmatiche dell'SDK - -Il sottopercorso `twenty-sdk/cli` esporta funzioni che puoi chiamare direttamente dal codice di test: - -| Funzione | Descrizione | -| -------------- | ----------------------------------------------- | -| `appBuild` | Compila l'app e, opzionalmente, crea un tarball | -| `appDeploy` | Carica un tarball sul server | -| `appInstall` | Installa l'app nello spazio di lavoro attivo | -| `appUninstall` | Disinstalla l'app dallo spazio di lavoro attivo | - -Ogni funzione restituisce un oggetto risultato con `success: boolean` e `data` oppure `error`. - -### Scrivere un test di integrazione - -Ecco un esempio completo che compila, distribuisce e installa l'app, quindi verifica che compaia nello spazio di lavoro: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Esecuzione dei test - -Assicurati che il tuo server Twenty locale sia in esecuzione, quindi: - -```bash filename="Terminal" -yarn test -``` - -Oppure in modalità watch durante lo sviluppo: - -```bash filename="Terminal" -yarn test:watch -``` - -### Controllo dei tipi - -Puoi anche eseguire il controllo dei tipi sulla tua app senza eseguire i test: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Questo esegue `tsc --noEmit` e riporta eventuali errori di tipo. - -## Riferimento CLI - -Oltre a `dev`, `build`, `add` e `typecheck`, la CLI fornisce comandi per eseguire funzioni, visualizzare i log e gestire le installazioni delle app. - -### Esecuzione delle funzioni (`yarn twenty exec`) - -Esegui manualmente una funzione logica senza attivarla tramite HTTP, cron o evento del database: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Visualizzazione dei log delle funzioni (`yarn twenty logs`) - -Esegui lo streaming dei log di esecuzione per le funzioni logiche della tua app: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-Vedi la [sezione sugli asset pubblici](/l/it/developers/extend/apps/cli-and-testing#public-assets-public-folder) per i dettagli.
-
-## Stile
-
-I componenti front-end supportano diversi approcci di styling. Puoi usare:
-
-* **Stili inline** — `style={{ color: 'red' }}`
-* **Componenti Twenty UI** — importali da `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar e altro)
-* **Emotion** — CSS-in-JS con `@emotion/react`
-* **Styled-components** — pattern `styled.div`
-* **Tailwind CSS** — classi di utilità
-* **Qualsiasi libreria CSS-in-JS** compatibile con React
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Como o empacotamento funciona - -A etapa de build usa o esbuild para produzir um único arquivo independente por função lógica e por componente de front-end. Todos os pacotes importados são incorporados ao bundle. - -**Funções lógicas** são executadas em um ambiente Node.js. Módulos nativos do Node (`fs`, `path`, `crypto`, `http`, etc.) estão disponíveis e não precisam ser instalados. - -**Componentes de front-end** são executados em um Web Worker. Módulos nativos do Node **não** estão disponíveis — apenas APIs do navegador e pacotes npm que funcionam em um ambiente de navegador. - -Ambos os ambientes têm `twenty-client-sdk/core` e `twenty-client-sdk/metadata` disponíveis como módulos pré-fornecidos — eles não são empacotados, mas resolvidos em tempo de execução pelo servidor. - -## Testando seu aplicativo - -O SDK fornece APIs programáticas que permitem compilar, implantar, instalar e desinstalar seu aplicativo a partir de código de teste. Em conjunto com [Vitest](https://vitest.dev/) e os clientes de API tipados, você pode escrever testes de integração que verificam que seu aplicativo funciona de ponta a ponta em um servidor Twenty real. - -### Configuração - -O aplicativo gerado pelo scaffolder já inclui o Vitest. Se você configurá-lo manualmente, instale as dependências: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Crie um `vitest.config.ts` na raiz do seu aplicativo: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Crie um arquivo de configuração que verifique se o servidor está acessível antes da execução dos testes: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### 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 | - -Cada função retorna um objeto de resultado com `success: boolean` e `data` ou `error`. - -### Escrevendo um teste de integração - -Aqui está um exemplo completo que compila, implanta e instala o aplicativo e, em seguida, verifica se ele aparece no espaço de trabalho: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Executando testes - -Certifique-se de que seu servidor Twenty local esteja em execução e, em seguida: - -```bash filename="Terminal" -yarn test -``` - -Ou no modo watch durante o desenvolvimento: - -```bash filename="Terminal" -yarn test:watch -``` - -### Verificação de tipos - -Você também pode executar a verificação de tipos no seu aplicativo sem executar os testes: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Isso executa `tsc --noEmit` e informa quaisquer erros de tipo. - -## Referência da CLI - -Além de `dev`, `build`, `add` e `typecheck`, a CLI fornece comandos para executar funções, visualizar logs e gerenciar instalações de aplicativos. - -### Executando funções (`yarn twenty exec`) - -Execute manualmente uma função de lógica sem acioná-la via HTTP, cron ou evento de banco de dados: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Visualizando logs de funções (`yarn twenty logs`) - -Transmita os logs de execução das funções de lógica do seu aplicativo: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -Export {selectedRecordIds.length} selected record(s)?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-Veja a [seção de recursos públicos](/l/pt/developers/extend/apps/cli-and-testing#public-assets-public-folder) para obter detalhes.
-
-## Estilização
-
-Componentes de front-end suportam várias abordagens de estilização. Você pode usar:
-
-* **Estilos inline** — `style={{ color: 'red' }}`
-* **Componentes de UI do Twenty** — importe de `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar e mais)
-* **Emotion** — CSS-in-JS com `@emotion/react`
-* **Styled-components** — padrões `styled.div`
-* **Tailwind CSS** — classes utilitárias
-* **Qualquer biblioteca CSS-in-JS** compatível com React
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Cum funcționează împachetarea - -Pasul de build folosește esbuild pentru a produce un singur fișier autonom pentru fiecare funcție logică și pentru fiecare componentă frontend. Toate pachetele importate sunt integrate în bundle. - -**Funcțiile logice** rulează într-un mediu Node.js. Modulele built-in Node (`fs`, `path`, `crypto`, `http` etc.) sunt disponibile și nu trebuie instalate. - -**Componentele frontend** rulează într-un Web Worker. Modulele built-in Node nu sunt disponibile — doar API-urile de browser și pachetele npm care funcționează într-un mediu de browser. - -Ambele medii au `twenty-client-sdk/core` și `twenty-client-sdk/metadata` disponibile ca module pre-furnizate — acestea nu sunt incluse în bundle, ci sunt rezolvate la rulare de către server. - -## Testarea aplicației - -SDK-ul oferă API-uri programatice care vă permit să construiți, să distribuiți, să instalați și să dezinstalați aplicația din codul de test. Combinat cu [Vitest](https://vitest.dev/) și clienții API tipizați, puteți scrie teste de integrare care verifică faptul că aplicația funcționează cap-coadă împotriva unui server Twenty real. - -### Configurare - -Aplicația generată (scaffolded) include deja Vitest. Dacă o configurați manual, instalați dependențele: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Creați un `vitest.config.ts` în rădăcina aplicației: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Creați un fișier de configurare care verifică faptul că serverul este accesibil înainte de rularea testelor: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### API-uri SDK programatice - -Subruta `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 | - -Fiecare funcție returnează un obiect rezultat cu `success: boolean` și fie `data`, fie `error`. - -### Scrierea unui test de integrare - -Iată un exemplu complet care construiește, distribuie și instalează aplicația, apoi verifică faptul că aceasta apare în spațiul de lucru: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Rularea testelor - -Asigurați-vă că serverul Twenty local rulează, apoi: - -```bash filename="Terminal" -yarn test -``` - -Sau în modul watch în timpul dezvoltării: - -```bash filename="Terminal" -yarn test:watch -``` - -### Verificarea tipurilor - -Puteți rula și verificarea tipurilor pe aplicație fără a rula testele: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Aceasta rulează `tsc --noEmit` și raportează orice erori de tip. - -## Referință CLI - -Dincolo de `dev`, `build`, `add` și `typecheck`, CLI oferă comenzi pentru executarea funcțiilor, vizualizarea jurnalelor și gestionarea instalărilor de aplicații. - -### Executarea funcțiilor (`yarn twenty exec`) - -Rulați manual o funcție logică fără a o declanșa prin HTTP, cron sau eveniment de bază de date: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Vizualizarea jurnalelor funcțiilor (`yarn twenty logs`) - -Transmiteți în flux jurnalele de execuție pentru funcțiile logice ale aplicației: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -Export {selectedRecordIds.length} selected record(s)?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-Consultați [secțiunea despre resurse publice](/l/ro/developers/extend/apps/cli-and-testing#public-assets-public-folder) pentru detalii.
-
-## Stilizare
-
-Componentele front-end acceptă mai multe abordări de stilizare. Puteți folosi:
-
-* **Stiluri inline** — `style={{ color: 'red' }}`
-* **Componente Twenty UI** — import din `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar și altele)
-* **Emotion** — CSS-in-JS cu `@emotion/react`
-* **Styled-components** — pattern-uri `styled.div`
-* **Tailwind CSS** — clase utilitare
-* **Orice bibliotecă CSS-in-JS** compatibilă cu React
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Как работает бандлинг - -Этап сборки использует esbuild для создания одного самодостаточного файла на каждую логическую функцию и на каждый компонент фронтенда. Все импортированные пакеты встроены в бандл. - -**Логические функции** выполняются в среде Node.js. Встроенные модули Node (`fs`, `path`, `crypto`, `http` и т. д.) доступны и не требуют установки. - -**Компоненты фронтенда** выполняются в Web Worker. Встроенные модули Node недоступны — доступны только браузерные API и пакеты npm, работающие в браузерной среде. - -В обеих средах доступны как предварительно предоставленные модули `twenty-client-sdk/core` и `twenty-client-sdk/metadata` — они не включаются в бандл, а подставляются сервером во время выполнения. - -## Тестирование вашего приложения - -SDK предоставляет программные API, которые позволяют собирать, разворачивать, устанавливать и удалять ваше приложение из тестового кода. В сочетании с [Vitest](https://vitest.dev/) и типизированными клиентами API вы можете писать интеграционные тесты, которые проверяют, что ваше приложение работает сквозным образом на реальном сервере Twenty. - -### Настройка - -Приложение, созданное скэффолдером, уже включает Vitest. Если вы настраиваете его вручную, установите зависимости: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Создайте `vitest.config.ts` в корне вашего приложения: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Создайте файл инициализации, который проверяет доступность сервера перед запуском тестов: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Программные API SDK - -Подпуть `twenty-sdk/cli` экспортирует функции, которые можно вызывать напрямую из тестового кода: - -| Функция | Описание | -| -------------- | ---------------------------------------------------------- | -| `appBuild` | Собрать приложение и при необходимости упаковать tar-архив | -| `appDeploy` | Загрузить tar-архив на сервер | -| `appInstall` | Установить приложение в активное рабочее пространство | -| `appUninstall` | Удалить приложение из активного рабочего пространства | - -Каждая функция возвращает объект результата с `success: boolean` и либо `data`, либо `error`. - -### Написание интеграционного теста - -Полный пример, который собирает, разворачивает и устанавливает приложение, а затем проверяет, что оно появляется в рабочем пространстве: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Запуск тестов - -Убедитесь, что ваш локальный сервер Twenty запущен, затем: - -```bash filename="Terminal" -yarn test -``` - -Или в режиме наблюдения во время разработки: - -```bash filename="Terminal" -yarn test:watch -``` - -### Проверка типов - -Вы также можете запустить проверку типов для своего приложения без запуска тестов: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Это запускает `tsc --noEmit` и сообщает о любых ошибках типов. - -## Справочник по CLI - -Помимо `dev`, `build`, `add` и `typecheck`, CLI предоставляет команды для выполнения функций, просмотра логов и управления установками приложений. - -### Выполнение функций (`yarn twenty exec`) - -Запустите логическую функцию вручную, не вызывая её через HTTP, cron или событие базы данных: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Просмотр логов функций (`yarn twenty logs`) - -Потоковая передача журналов выполнения логических функций вашего приложения: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -Export {selectedRecordIds.length} selected record(s)?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-См. [раздел о публичных ресурсах](/l/ru/developers/extend/apps/cli-and-testing#public-assets-public-folder) для подробностей.
-
-## Стилизация
-
-Компоненты фронтенда поддерживают несколько подходов к стилизации. Вы можете использовать:
-
-* **Встроенные стили** — `style={{ color: 'red' }}`
-* **Компоненты Twenty UI** — импорт из `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar и другие)
-* **Emotion** — CSS-in-JS с `@emotion/react`
-* **Styled-components** — паттерны `styled.div`
-* **Tailwind CSS** — утилитарные классы
-* **Любая библиотека CSS-in-JS**, совместимая с React
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Paketleme nasıl çalışır - -Derleme adımı, her mantık işlevi ve her ön uç bileşeni için tek bir bağımsız dosya üretmek üzere esbuild kullanır. Tüm içe aktarılan paketler pakete satır içi eklenir. - -**Mantık işlevleri**, Node.js ortamında çalışır. Node yerleşik modülleri (`fs`, `path`, `crypto`, `http` vb.) kullanılabilir ve kurulmaları gerekmez. - -**Ön uç bileşenleri**, bir Web Worker içinde çalışır. Node'un yerleşik modülleri **kullanılamaz** — yalnızca tarayıcı ortamında çalışan tarayıcı API'leri ve npm paketleri kullanılabilir. - -Her iki ortamda da `twenty-client-sdk/core` ve `twenty-client-sdk/metadata` önceden sağlanmış modüller olarak mevcuttur — bunlar paketlenmez, ancak çalışma zamanında sunucu tarafından çözülür. - -## Uygulamanızı test etme - -SDK, test kodundan uygulamanızı derlemenize, dağıtmanıza, yüklemenize ve kaldırmanıza olanak tanıyan programatik API'ler sağlar. Tiplenmiş API istemcileriyle birlikte [Vitest](https://vitest.dev/) kullanarak, uygulamanızın gerçek bir Twenty sunucusunda uçtan uca çalıştığını doğrulayan entegrasyon testleri yazabilirsiniz. - -### Kurulum - -İskelet aracıyla oluşturulan uygulama zaten Vitest'i içerir. Manuel kurulum yaparsanız, bağımlılıkları yükleyin: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Uygulamanızın kök dizininde bir `vitest.config.ts` oluşturun: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Testler çalışmadan önce sunucuya erişilebildiğini doğrulayan bir kurulum dosyası oluşturun: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Programatik SDK API'leri - -`twenty-sdk/cli` alt yolu, test kodundan doğrudan çağırabileceğiniz fonksiyonları dışa aktarır: - -| Fonksiyon | Açıklama | -| -------------- | ----------------------------------------------------------------- | -| `appBuild` | Uygulamayı derleyin ve isteğe bağlı olarak bir tarball paketleyin | -| `appDeploy` | Bir tarball'ı sunucuya yükleyin | -| `appInstall` | Uygulamayı etkin çalışma alanına yükleyin | -| `appUninstall` | Uygulamayı etkin çalışma alanından kaldırın | - -Her fonksiyon, `success: boolean` ile birlikte `data` veya `error` içeren bir sonuç nesnesi döndürür. - -### Bir entegrasyon testi yazma - -İşte uygulamayı derleyen, dağıtan ve yükleyen; ardından çalışma alanında göründüğünü doğrulayan tam bir örnek: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Testleri çalıştırma - -Yerel Twenty sunucunuzun çalıştığından emin olun, ardından: - -```bash filename="Terminal" -yarn test -``` - -Veya geliştirme sırasında izleme modunda: - -```bash filename="Terminal" -yarn test:watch -``` - -### Tip denetimi - -Ayrıca testleri çalıştırmadan uygulamanızda tip denetimi çalıştırabilirsiniz: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Bu, `tsc --noEmit` komutunu çalıştırır ve tüm tip hatalarını raporlar. - -## CLI başvurusu - -`dev`, `build`, `add` ve `typecheck` dışında CLI, fonksiyonları çalıştırma, günlükleri görüntüleme ve uygulama kurulumlarını yönetme komutları sağlar. - -### Fonksiyonları çalıştırma (`yarn twenty exec`) - -Bir mantık fonksiyonunu HTTP, cron veya veritabanı olayıyla tetiklemeden manuel olarak çalıştırın: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Fonksiyon günlüklerini görüntüleme (`yarn twenty logs`) - -Uygulamanızın mantık fonksiyonlarının yürütme günlüklerini akış olarak alın: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -Export {selectedRecordIds.length} selected record(s)?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-Ayrıntılar için [genel varlıklar bölümüne](/l/tr/developers/extend/apps/cli-and-testing#public-assets-public-folder) bakın.
-
-## Stil
-
-Ön uç bileşenleri birden fazla biçimlendirme yaklaşımını destekler. Şunları kullanabilirsiniz:
-
-* **Satır içi stiller** — `style={{ color: 'red' }}`
-* **Twenty UI bileşenleri** — `twenty-sdk/ui` içinden içe aktarın (Button, Tag, Status, Chip, Avatar ve daha fazlası)
-* **Emotion** — `@emotion/react` ile CSS-in-JS
-* **Styled-components** — `styled.div` kalıpları
-* **Tailwind CSS** — yardımcı sınıflar
-* **React ile uyumlu herhangi bir CSS-in-JS kitaplığı**
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-Today is {format(new Date(), 'MMMM do, yyyy')}
; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### 打包的工作原理 - -构建步骤使用 esbuild 为每个逻辑函数和每个前端组件生成一个自包含文件。 所有导入的包都会被内联到打包产物中。 - -**逻辑函数** 运行在 Node.js 环境中。 Node 内置模块(`fs`、`path`、`crypto`、`http` 等) 可用且无需安装。 - -**前端组件** 运行在 Web Worker 中。 Node 内置模块不可用——仅可使用浏览器 API 以及可在浏览器环境中运行的 npm 包。 - -两个环境都将 `twenty-client-sdk/core` 和 `twenty-client-sdk/metadata` 作为预置模块提供 — 这些模块不会被打包,而是在运行时由服务器解析。 - -## 测试你的应用 - -该 SDK 提供可编程的 API,使你可以在测试代码中构建、部署、安装和卸载你的应用。 结合 [Vitest](https://vitest.dev/) 和类型化 API 客户端,你可以编写集成测试,在真实的 Twenty 服务器上验证你的应用端到端运行是否正常。 - -### 设置 - -脚手架生成的应用已包含 Vitest。 如果你手动进行设置,请安装这些依赖: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -在应用根目录下创建一个 `vitest.config.ts`: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -创建一个设置文件,在测试运行前验证服务器可达: - -```ts src/__tests__/setup-test.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'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### 可编程的 SDK API - -子路径 `twenty-sdk/cli` 导出了可直接在测试代码中调用的函数: - -| 函数 | 描述 | -| -------------- | ------------------ | -| `appBuild` | 构建应用,并可选地打包为 tar 包 | -| `appDeploy` | 将 tar 包上传到服务器 | -| `appInstall` | 在活动工作区安装该应用 | -| `appUninstall` | 从活动工作区卸载该应用 | - -每个函数都会返回一个结果对象,包含 `success: boolean`,以及 `data` 或 `error` 之一。 - -### 编写集成测试 - -下面是一个完整示例:构建、部署并安装该应用,然后验证它出现在工作区中: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### 运行测试 - -确保你的本地 Twenty 服务器正在运行,然后: - -```bash filename="Terminal" -yarn test -``` - -或者在开发期间使用监听模式: - -```bash filename="Terminal" -yarn test:watch -``` - -### 类型检查 - -你也可以在不运行测试的情况下对应用进行类型检查: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -这会运行 `tsc --noEmit` 并报告所有类型错误。 - -## CLI 参考 - -除了 `dev`、`build`、`add` 和 `typecheck` 外,CLI 还提供了用于执行函数、查看日志和管理应用安装的命令。 - -### 执行函数(`yarn twenty exec`) - -手动运行逻辑函数,而无需通过 HTTP、定时任务或数据库事件来触发: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### 查看函数日志(`yarn twenty logs`) - -实时流式查看你的应用逻辑函数的执行日志: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -This component renders inside Twenty.
-
-User: {userId}
-Record: {recordId ?? 'No record context'}
-Component: {componentId}
-Archive this record?
- -Export {selectedRecordIds.length} selected record(s)?
- -
;
-
-export default defineFrontComponent({
- universalIdentifier: '...',
- name: 'logo',
- component: Logo,
-});
-```
-
-详情请参见[公共资源部分](/l/zh/developers/extend/apps/cli-and-testing#public-assets-public-folder)。
-
-## 样式
-
-前端组件支持多种样式方案。 您可以使用:
-
-* **内联样式** — `style={{ color: 'red' }}`
-* **Twenty UI 组件** — 从 `twenty-sdk/ui` 导入(Button、Tag、Status、Chip、Avatar 等)
-* **Emotion** — 使用 `@emotion/react` 的 CSS-in-JS
-* **Styled-components** — `styled.div` 模式
-* **Tailwind CSS** — 工具类
-* **任何 CSS-in-JS 库**(与 React 兼容)
-
-```tsx
-import { defineFrontComponent } from 'twenty-sdk/define';
-import { Button, Tag, Status } from 'twenty-sdk/ui';
-
-const StyledWidget = () => {
- return (
-
-
-
-
-
-
-
-
-