+
+
+
+
+
+
+
+This component renders inside Twenty.
+
+User: {userId}
+Record: {recordId ?? 'No record context'}
+Component: {componentId}
+Hello, {recipientName}!
; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'greeting', + component: Greeting, +}); +``` + +Archive this record?
+ +Export {selectedRecordIds.length} selected record(s)?
+ +
;
+
+export default defineFrontComponent({
+ universalIdentifier: '...',
+ name: 'logo',
+ component: Logo,
+});
+```
+
+راجع [قسم الأصول العامة](/l/ar/developers/extend/apps/config/public-assets) للتفاصيل.
+
+## التنسيق
+
+تدعم المكوّنات الأمامية عدة أساليب للتنسيق. يمكنك استخدام:
+
+* **أنماط مضمنة** — `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, +}); +``` + +### كيف يعمل التجميع + +تستخدم خطوة البناء أداة esbuild لإنتاج ملف واحد مستقل لكل دالة منطقية ولكل مكوّن أمامي. تُضمَّن جميع الحزم المستوردة داخل الحزمة. + +**الدوال المنطقية** تعمل في بيئة Node.js. الوحدات المدمجة في Node (`fs` و`path` و`crypto` و`http` وغيرها) متاحة ولا تحتاج إلى تثبيت. + +**المكوّنات الأمامية** تعمل ضمن Web Worker. وحدات Node المدمجة غير متاحة — المتاح فقط واجهات برمجة المتصفّح وحِزَم npm التي تعمل في بيئة المتصفّح. + +كلتا البيئتين تحتويان على `twenty-client-sdk/core` و`twenty-client-sdk/metadata` كوحدات متاحة مُسبقًا — لا تُضمَّن هذه ضمن الحزم بل تُحلّ وقت التشغيل بواسطة الخادم. + +## إعداد + +يتضمّن التطبيق المُولَّد بالقالب بالفعل 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 dev:typecheck +``` + +يشغِّل هذا الأمر `tsc --noEmit` ويبلغ عن أي أخطاء في الأنواع. + +## التكامل المستمر (CI) باستخدام GitHub Actions + +تولّد أداة إنشاء الهيكل سير عمل GitHub Actions جاهزًا للاستخدام في `.github/workflows/ci.yml`. يشغّل اختبارات التكامل لديك تلقائيًا عند كل دفع إلى `main` وعلى طلبات السحب. + +سير العمل: + +1. يجلب الشيفرة الخاصة بك +2. يشغّل خادم Twenty مؤقتًا باستخدام الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. يثبّت التبعيات باستخدام `yarn install --immutable` +4. يشغّل `yarn test` مع حقن `TWENTY_API_URL` و`TWENTY_API_KEY` من مخرجات الإجراء + +```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 }} +``` + +لا تحتاج إلى تهيئة أي أسرار — إذ يبدأ إجراء `spawn-twenty-docker-image` خادم Twenty عابرًا مباشرة في المشغّل ويُخرِج تفاصيل الاتصال. يتم توفير السر `GITHUB_TOKEN` تلقائيًا من قِبل GitHub. + +لتثبيت إصدار محدّد من Twenty بدلًا من `latest`، غيّر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل. diff --git a/packages/twenty-docs/l/ar/developers/extend/capabilities/apis.mdx b/packages/twenty-docs/l/ar/developers/extend/capabilities/apis.mdx index 6039d0e7f5..fb54564d1d 100644 --- a/packages/twenty-docs/l/ar/developers/extend/capabilities/apis.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/capabilities/apis.mdx @@ -88,7 +88,7 @@ Authorization: Bearer YOUR_API_KEY لتحسين الأمان، عيّن دوراً محدداً لتقييد الوصول: -1. اذهب إلى **الإعدادات → الأدوار** +1. انتقل إلى **الإعدادات → الأعضاء → الأدوار** 2. انقر على الدور الذي ترغب في تعيينه 3. افتح علامة التبويب **التعيين** 4. ضمن **مفاتيح API**، انقر على **+ تعيين إلى مفتاح API** diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx index 3d8c0eb0ad..741c332483 100644 --- a/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx @@ -51,7 +51,7 @@ VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent. curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example ``` -2. **إنشاء رموز سرية** +2. **إنشاء مفتاح تشفير** قم بتشغيل الأمر التالي لإنشاء سلسلة عشوائية فريدة: @@ -59,16 +59,18 @@ VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent. openssl rand -base64 32 ``` - **مهم:** احتفظ بهذه القيمة سرية ولا تشاركها. + **مهم:** احتفظ بهذه القيمة سرية ولا تشاركها. فقدان `ENCRYPTION_KEY` يعني فقدان الوصول إلى كل سر مخزَّن في قاعدة البيانات (رموز OAuth، متغيرات التطبيق، أسرار TOTP، إلخ). 3. **تحديث الـ `.env`** استبدل قيمة النائب في ملف .env بالقيمة الرمزية المولدة: ```ini - APP_SECRET=first_random_string + ENCRYPTION_KEY=random_string ``` + راجع [دليل تدوير المفاتيح](/l/ar/developers/self-host/capabilities/key-rotation) للحصول على إرشادات حول تدويره بدون توقّف عن العمل. + 4. **تعيين كلمة مرور PostgreSQL** قم بتحديث قيمة `PG_DATABASE_PASSWORD` في ملف .env باستخدام كلمة مرور قوية بدون أحرف خاصة. diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/key-rotation.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/key-rotation.mdx new file mode 100644 index 0000000000..486bd0d241 --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/key-rotation.mdx @@ -0,0 +1,60 @@ +--- +title: تدوير المفاتيح +icon: rotate +--- + +يمتلك Twenty عائلتين مستقلتين من المفاتيح: + +* **مفاتيح توقيع JWT** — أزواج مفاتيح غير متماثلة ES256 (مع علامة `kid`) مُخزَّنة في `core."signingKey"`، تُستخدم لتوقيع والتحقق من رموز الوصول / التحديث. +* **مفتاح التشفير أثناء السكون (At-rest encryption key)** — `ENCRYPTION_KEY`، يُستخدم لتشفير رموز OAuth، ومتغيرات التطبيق، ومفاتيح التوقيع الخاصة، وقيم الإعدادات الحساسة، وأسرار TOTP داخل غلاف `enc:v2:`. + +يُعد `APP_SECRET` سراً قديماً مُحتفَظاً به لأغراض التوافق مع الإصدارات السابقة: عندما لا يكون `ENCRYPTION_KEY` مضبوطاً، فإنه يعمل كحل احتياطي لمفتاح التشفير أثناء السكون / ملفات تعريف الارتباط للجلسة، ولا يزال يتحقق من رموز الوصول HS256 الموجودة مسبقاً. سيتم إهماله (إيقاف دعمه). + +## مفاتيح توقيع JWT + +يحمل كل مفتاح قيمة `publicKey` (يُحتفَظ بها إلى أجل غير مسمى حتى يمكنها التحقق من الرموز المصدرة مسبقاً)، و`privateKey` مُشفَّراً (يُستخدم فقط أثناء كون المفتاح حالياً)، وراية `isCurrent` (صف واحد فقط في كل وقت)، وحقل `revokedAt` اختياري. + +### تدوير المفتاح الحالي + +اضبط `SIGNING_KEY_ROTATION_DAYS` للتفعيل: عندها تصدر مهمة cron يومية مفتاحًا حاليًا جديدًا بمجرد أن يصبح المفتاح القائم أقدم من تلك العتبة. لا يتم إبطال المفاتيح السابقة، لذلك تستمر الرموز الموقعة تحتها في التحقق. اترك المتغير غير معيّن لتعطيل التدوير التلقائي. + +
-4A4F33452D 44445245332A2E2F454A46 28452F 482532274429 35483129.
+Allows users to upload and remove an image.
+
-2. Click **Get Enterprise Key**
-3. When you are redirected to Stripe, enter your payment details and confirm
-4. When your Enterprise key is displayed, paste it into the Enterprise settings page and activate the Organization license
+2. انقر **احصل على مفتاح Enterprise**
+3. عند إعادة توجيهك إلى Stripe، أدخل تفاصيل الدفع الخاصة بك وأكّد
+4. عند عرض مفتاح Enterprise الخاص بك، الصقه في صفحة إعدادات Enterprise وقم بتفعيل ترخيص Organization
diff --git a/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx
index cf2f7015c6..d6a9d4abda 100644
--- a/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx
+++ b/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx
@@ -25,15 +25,25 @@ description: Connect your email and calendar accounts to Twenty.
6. Configure calendar sync settings (visibility, auto-creation) → click **Add Account**
7. ستبدأ رسائل البريد الإلكتروني وفعاليات التقويم بالمزامنة تلقائيًا
-### إعداد SMTP/CalDAV (مزودون آخرون)
+### إعداد IMAP/SMTP/CalDAV (مزودون آخرون)
بالنسبة لمزودي البريد الإلكتروني والتقويم الآخرين:
1. اذهب إلى **الإعدادات → الحسابات**
-2. قم بتكوين إعدادات SMTP للبريد الإلكتروني
+2. قم بتهيئة إعدادات IMAP لمزامنة رسائل البريد الإلكتروني الواردة وإعدادات SMTP لإرسال البريد الإلكتروني
3. قم بتكوين إعدادات CalDAV للتقويم
4. اختبر الاتصال
+
+
+
+
+
+
+
+
+This component renders inside Twenty.
+
+User: {userId}
+Record: {recordId ?? 'No record context'}
+Component: {componentId}
+Hello, {recipientName}!
; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'greeting', + component: Greeting, +}); +``` + +Archive this record?
+ +Export {selectedRecordIds.length} selected record(s)?
+ +
;
+
+export default defineFrontComponent({
+ universalIdentifier: '...',
+ name: 'logo',
+ component: Logo,
+});
+```
+
+Podrobnosti viz [sekci veřejných souborů](/l/cs/developers/extend/apps/config/public-assets).
+
+## Stylování
+
+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, +}); +``` + +### 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á. + +## 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 dev:typecheck +``` + +Spustí se `tsc --noEmit` a nahlásí se případné chyby typů. + +## CI s GitHub Actions + +Generátor kostry vytvoří připravený k použití workflow GitHub Actions v `.github/workflows/ci.yml`. Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů. + +Workflow: + +1. Načte váš kód (checkout). +2. Spustí dočasný server Twenty pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Nainstaluje závislosti pomocí `yarn install --immutable` +4. Spustí `yarn test` s proměnnými `TWENTY_API_URL` a `TWENTY_API_KEY` vloženými z výstupů akce + +```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 }} +``` + +Není potřeba konfigurovat žádné secrets — akce `spawn-twenty-docker-image` spustí dočasný server Twenty přímo v runneru a vypíše podrobnosti připojení. Secret `GITHUB_TOKEN` je poskytován GitHubem automaticky. + +Chcete-li připnout konkrétní verzi Twenty místo `latest`, změňte proměnnou prostředí `TWENTY_VERSION` na začátku workflow. diff --git a/packages/twenty-docs/l/cs/developers/extend/capabilities/apis.mdx b/packages/twenty-docs/l/cs/developers/extend/capabilities/apis.mdx index e9f738487e..b6406f571f 100644 --- a/packages/twenty-docs/l/cs/developers/extend/capabilities/apis.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/capabilities/apis.mdx @@ -88,7 +88,7 @@ Váš klíč API poskytuje přístup k citlivým datům. Nesdílejte ho s nedův Pro vyšší bezpečnost přiřaďte konkrétní roli, abyste omezili přístup: -1. Přejděte na **Nastavení → Role** +1. Přejděte na **Nastavení → Členové → Role** 2. Klikněte na roli, kterou chcete přiřadit 3. Otevřete záložku **Přiřazení** 4. V části **API Keys** klikněte na **+ Přiřadit ke klíči API** diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx index 76b0fbe650..e7a3f7c46d 100644 --- a/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx @@ -51,7 +51,7 @@ Postupujte podle těchto kroků pro ruční nastavení. curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example ``` -2. **Vygenerujte tajné tokeny** +2. **Vygenerujte šifrovací klíč** Spusťte následující příkaz k generování jedinečného náhodného řetězce: @@ -59,16 +59,18 @@ Postupujte podle těchto kroků pro ruční nastavení. openssl rand -base64 32 ``` - **Důležité:** Udržujte tuto hodnotu v tajnosti / nesdílejte ji. + **Důležité:** Udržujte tuto hodnotu v tajnosti / nesdílejte ji. Ztráta `ENCRYPTION_KEY` znamená ztrátu přístupu ke všem tajným údajům uloženým v databázi (OAuth tokeny, aplikační proměnné, TOTP tajemství atd.). 3. **Aktualizujte `.env` soubor** Nahraďte místoblokovou hodnotu ve svém .env souboru vygenerovaným tokenem: ```ini - APP_SECRET=první_náhodný_řetězec + ENCRYPTION_KEY=random_string ``` + Podívejte se na [průvodce rotací klíče](/l/cs/developers/self-host/capabilities/key-rotation) pro pokyny, jak jej rotovat bez prostojů. + 4. **Nastavte Heslo pro Postgres** Aktualizujte hodnotu `PG_DATABASE_PASSWORD` ve vašem .env souboru silným heslem bez speciálních znaků. diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/key-rotation.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/key-rotation.mdx new file mode 100644 index 0000000000..3b48efc2bf --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/key-rotation.mdx @@ -0,0 +1,60 @@ +--- +title: Rotace klíčů +icon: rotate +--- + +Twenty má dvě nezávislé rodiny klíčů: + +* **Klíče pro podepisování JWT** — asymetrické páry klíčů ES256 (označené `kid`), uložené v `core."signingKey"`, používané k podepisování a ověřování přístupových/obnovovacích tokenů. +* **Šifrovací klíč pro data v klidu** — `ENCRYPTION_KEY`, používaný k šifrování OAuth tokenů, aplikačních proměnných, soukromých klíčů podepisovacích klíčů, citlivých konfiguračních hodnot a TOTP tajemství uvnitř obálky `enc:v2:`. + +`APP_SECRET` je starší tajný klíč ponechaný kvůli zpětné kompatibilitě: pokud `ENCRYPTION_KEY` není nastaven, slouží jako záložní řešení pro šifrování dat v klidu i pro soubory cookie relace a stále ověřuje dříve existující HS256 přístupové tokeny. Bude označen jako zastaralý (deprecated). + +## Klíče pro podepisování JWT + +Každý klíč nese `publicKey` (uchovávaný neomezeně dlouho, aby mohl ověřovat dříve vydané tokeny), zašifrovaný `privateKey` (používaný pouze tehdy, když je klíč aktuální), příznak `isCurrent` (vždy přesně jeden záznam) a volitelné `revokedAt`. + +### Rotace aktuálního klíče + +Nastavte `SIGNING_KEY_ROTATION_DAYS`, chcete-li funkci povolit: denní cron poté vydá nový klíč jako aktuální, jakmile je stávající starší než zadaný práh. Předchozí klíče *nejsou* odvolány, takže tokeny pod nimi podepsané se dál úspěšně ověřují. Ponechte proměnnou nenastavenou, abyste deaktivovali automatickou rotaci. + +
+
-2. Click **Get Enterprise Key**
-3. When you are redirected to Stripe, enter your payment details and confirm
-4. When your Enterprise key is displayed, paste it into the Enterprise settings page and activate the Organization license
+2. Klikněte na **Získat klíč Enterprise**
+3. Po přesměrování na Stripe zadejte platební údaje a potvrďte
+4. Jakmile se zobrazí váš klíč Enterprise, vložte jej na stránku nastavení Enterprise a aktivujte licenci Organization
diff --git a/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx
index 5c2cc77b01..db08603d89 100644
--- a/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx
+++ b/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx
@@ -25,15 +25,25 @@ description: Připojte své účty e-mailu a kalendáře k Twenty.
6. Nastavte synchronizaci kalendáře (viditelnost, automatické vytváření) → klikněte na **Přidat účet**
7. Vaše e-maily a události v kalendáři se začnou synchronizovat automaticky
-### Nastavení SMTP/CalDAV (Další Poskytovatelé)
+### Nastavení IMAP/SMTP/CalDAV (Další poskytovatelé)
Pro další poskytovatele emailu a kalendáře:
1. Přejděte na **Nastavení → Účty**
-2. Nakonfigurujte nastavení SMTP pro email
+2. Nakonfigurujte nastavení IMAP pro synchronizaci příchozích e-mailů a nastavení SMTP pro odesílání e-mailů
3. Nakonfigurujte nastavení CalDAV pro kalendář
4. Otestujte připojení
+
+
+
+
+
+
+
+
+
+
+
+This component renders inside Twenty.
+
+User: {userId}
+Record: {recordId ?? 'No record context'}
+Component: {componentId}
+Hello, {recipientName}!
; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'greeting', + component: Greeting, +}); +``` + +Archive this record?
+ +Export {selectedRecordIds.length} selected record(s)?
+ +
;
+
+export default defineFrontComponent({
+ universalIdentifier: '...',
+ name: 'logo',
+ component: Logo,
+});
+```
+
+Consulta la [sección de recursos públicos](/l/es/developers/extend/apps/config/public-assets) para más detalles.
+
+## Estilo
+
+Los componentes de frontend admiten varios enfoques de estilos. Puedes usar:
+
+* **Estilos en línea** — `style={{ color: 'red' }}`
+* **Componentes de Twenty UI** — importa desde `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar y más)
+* **Emotion** — CSS-in-JS con `@emotion/react`
+* **Styled-components** — patrones de `styled.div`
+* **Tailwind CSS** — clases utilitarias
+* **Cualquier librería CSS-in-JS** compatible 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, +}); +``` + +### Cómo funciona el empaquetado + +El paso de compilación usa esbuild para producir un solo archivo autónomo por función de lógica y por componente de frontend. Todos los paquetes importados se insertan en el bundle. + +**Las funciones de lógica** se ejecutan en un entorno Node.js. Los módulos integrados de Node (`fs`, `path`, `crypto`, `http`, etc.) están disponibles y no necesitan instalarse. + +**Los componentes de frontend** se ejecutan en un Web Worker. Los módulos integrados de Node **no** están disponibles — solo las APIs del navegador y paquetes de npm que funcionen en un entorno de navegador. + +Ambos entornos tienen `twenty-client-sdk/core` y `twenty-client-sdk/metadata` disponibles como módulos preproporcionados — estos no se incluyen en el bundle sino que se resuelven en tiempo de ejecución por el servidor. + +## Configuración + +La aplicación generada ya incluye Vitest. Si lo configuras manualmente, instala las dependencias: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Crea un `vitest.config.ts` en la raíz de tu aplicación: + +```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 archivo de configuración que verifique que el servidor es accesible antes de ejecutar las pruebas: + +```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 del SDK + +La subruta `twenty-sdk/cli` exporta funciones que puedes invocar directamente desde el código de pruebas: + +| Función | Descripción | +| -------------- | ------------------------------------------------------------ | +| `appBuild` | Compilar la aplicación y opcionalmente empaquetar un tarball | +| `appDeploy` | Subir un tarball al servidor | +| `appInstall` | Instalar la aplicación en el espacio de trabajo activo | +| `appUninstall` | Desinstalar la aplicación del espacio de trabajo activo | + +Cada función devuelve un objeto de resultado con `success: boolean` y `data` o `error`. + +## Escribir una prueba de integración + +Aquí tienes un ejemplo completo que compila, despliega e instala la aplicación, y luego verifica que aparezca en el espacio de trabajo: + +```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(); + }); +}); +``` + +## Ejecutar pruebas + +Asegúrate de que tu servidor local de Twenty esté en ejecución y luego: + +```bash filename="Terminal" +yarn test +``` + +O en modo watch durante el desarrollo: + +```bash filename="Terminal" +yarn test:watch +``` + +## Comprobación de tipos + +También puedes ejecutar la comprobación de tipos en tu aplicación sin ejecutar pruebas: + +```bash filename="Terminal" +yarn twenty dev:typecheck +``` + +Esto ejecuta `tsc --noEmit` e informa cualquier error de tipo. + +## CI con GitHub Actions + +El generador crea un flujo de trabajo de GitHub Actions listo para usar en `.github/workflows/ci.yml`. Ejecuta tus pruebas de integración automáticamente en cada push a `main` y en los pull requests. + +El flujo de trabajo: + +1. Obtiene tu código +2. Inicia un servidor temporal de Twenty usando la acción `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Instala las dependencias con `yarn install --immutable` +4. Ejecuta `yarn test` con `TWENTY_API_URL` y `TWENTY_API_KEY` inyectados a partir de las salidas de la acción + +```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 }} +``` + +No necesitas configurar secretos: la acción `spawn-twenty-docker-image` inicia un servidor efímero de Twenty directamente en el runner y devuelve los detalles de conexión. El secreto `GITHUB_TOKEN` lo proporciona GitHub automáticamente. + +Para fijar una versión específica de Twenty en lugar de `latest`, cambia la variable de entorno `TWENTY_VERSION` al inicio del flujo de trabajo. diff --git a/packages/twenty-docs/l/es/developers/extend/capabilities/apis.mdx b/packages/twenty-docs/l/es/developers/extend/capabilities/apis.mdx index 65f08139df..5f4f2a53dc 100644 --- a/packages/twenty-docs/l/es/developers/extend/capabilities/apis.mdx +++ b/packages/twenty-docs/l/es/developers/extend/capabilities/apis.mdx @@ -17,7 +17,7 @@ Twenty genera APIs específicamente para tu modelo de datos: * **Documentación personalizada**: Generada específicamente para el modelo de datos de tu espacio de trabajo.- Customer Insights -
-+ Customer Insights +
+
+
+2. Haga clic en **Obtener clave Enterprise**
+3. Cuando sea redirigido a Stripe, introduzca sus datos de pago y confirme
+4. Cuando se muestre su clave Enterprise, péguela en la página de configuración de Enterprise y active la licencia de Organization
diff --git a/packages/twenty-docs/l/es/user-guide/billing/how-tos/billing-faq.mdx b/packages/twenty-docs/l/es/user-guide/billing/how-tos/billing-faq.mdx
index 8e4c4cdc3e..9789ab69e2 100644
--- a/packages/twenty-docs/l/es/user-guide/billing/how-tos/billing-faq.mdx
+++ b/packages/twenty-docs/l/es/user-guide/billing/how-tos/billing-faq.mdx
@@ -6,81 +6,83 @@ description: Preguntas frecuentes sobre los precios y la facturación de Twenty.
## Precios
+
**Ideal para:**
@@ -30,7 +30,7 @@ Muestra los datos como barras horizontales o verticales.
* Contactos agregados por mes
+
**Ideal para:**
@@ -58,7 +58,7 @@ Muestra las proporciones de un todo.
Muestra las tendencias a lo largo del tiempo.
-
+
**Ideal para:**
@@ -78,7 +78,7 @@ Muestra las tendencias a lo largo del tiempo.
Muestra valores clave individuales de forma destacada.
-
+
**Ideal para:**
@@ -103,7 +103,7 @@ Muestra valores clave individuales de forma destacada.
Incorpora herramientas y contenido externos directamente en tu panel.
-
+
**Ideal para:**
@@ -123,7 +123,7 @@ Incorpora herramientas y contenido externos directamente en tu panel.
Añade texto con formato y contenido directamente a tu panel.
-
+
**Ideal para:**
@@ -139,7 +139,7 @@ Añade texto con formato y contenido directamente a tu panel.
* Edición con estilo Markdown
+
+
+
+
+
+
+
+This component renders inside Twenty.
+
+User: {userId}
+Record: {recordId ?? 'No record context'}
+Component: {componentId}
+Hello, {recipientName}!
; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'greeting', + component: Greeting, +}); +``` + +Archive this record?
+Export {selectedRecordIds.length} selected record(s)?
+
;
+
+export default defineFrontComponent({
+ universalIdentifier: '...',
+ name: 'logo',
+ component: Logo,
+});
+```
+
+Voir la [section sur les ressources publiques](/l/fr/developers/extend/apps/config/public-assets) pour plus de détails.
+
+## Stylisation
+
+Les composants frontaux prennent en charge plusieurs approches de stylisation. Vous pouvez utiliser :
+
+* **Styles en ligne** — `style={{ color: 'red' }}`
+* **Composants Twenty UI** — à importer depuis `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, etc.)
+* **Emotion** — CSS-in-JS avec `@emotion/react`
+* **Styled-components** — modèles `styled.div`
+* **Tailwind CSS** — classes utilitaires
+* **Toute bibliothèque CSS-in-JS** compatible avec 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, +}); +``` + +### Comment fonctionne le bundling + +L'étape de build utilise esbuild pour produire un seul fichier autonome par fonction logique et par composant frontal. Tous les packages importés sont intégrés dans le bundle. + +**Les fonctions logiques** s'exécutent dans un environnement Node.js. Les modules intégrés de Node (`fs`, `path`, `crypto`, `http`, etc.) sont disponibles et n'ont pas besoin d'être installés. + +**Les composants frontaux** s'exécutent dans un Web Worker. Les modules intégrés de Node ne sont **pas** disponibles — seules les API du navigateur et les packages npm qui fonctionnent dans un environnement navigateur sont pris en charge. + +Les deux environnements disposent de `twenty-client-sdk/core` et `twenty-client-sdk/metadata` en tant que modules pré-fournis — ils ne sont pas intégrés au bundle mais résolus à l'exécution par le serveur. + +## Installation + +L'application générée inclut déjà Vitest. Si vous le configurez manuellement, installez les dépendances : + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Créez un `vitest.config.ts` à la racine de votre application : + +```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', + }, + }, +}); +``` + +Créez un fichier de configuration qui vérifie que le serveur est joignable avant l'exécution des tests : + +```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 programmatiques du SDK + +Le sous-chemin `twenty-sdk/cli` exporte des fonctions que vous pouvez appeler directement depuis le code de test : + +| Fonction | Description | +| -------------- | -------------------------------------------------------------------- | +| `appBuild` | Construire l'application et éventuellement créer une archive tarball | +| `appDeploy` | Téléverser une archive tarball vers le serveur | +| `appInstall` | Installer l'application sur l'espace de travail actif | +| `appUninstall` | Désinstaller l'application de l'espace de travail actif | + +Chaque fonction retourne un objet résultat avec `success: boolean` et soit `data` soit `error`. + +## Écrire un test d'intégration + +Voici un exemple complet qui construit, déploie et installe l'application, puis vérifie qu'elle apparaît dans l'espace de travail : + +```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(); + }); +}); +``` + +## Exécuter les tests + +Assurez-vous que votre serveur Twenty local est en cours d'exécution, puis : + +```bash filename="Terminal" +yarn test +``` + +Ou en mode surveillance (watch) pendant le développement : + +```bash filename="Terminal" +yarn test:watch +``` + +## Vérification des types + +Vous pouvez également exécuter une vérification des types sur votre application sans exécuter les tests : + +```bash filename="Terminal" +yarn twenty dev:typecheck +``` + +Cela exécute `tsc --noEmit` et signale toute erreur de type. + +## CI avec GitHub Actions + +Le générateur crée un workflow GitHub Actions prêt à l’emploi dans `.github/workflows/ci.yml`. Il exécute automatiquement vos tests d’intégration à chaque push sur `main` et sur les pull requests. + +Le workflow : + +1. Récupère votre code +2. Lance un serveur Twenty temporaire en utilisant l’action `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Installe les dépendances avec `yarn install --immutable` +4. Exécute `yarn test` avec `TWENTY_API_URL` et `TWENTY_API_KEY` injectés à partir des sorties de l’action + +```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 }} +``` + +Vous n’avez pas besoin de configurer de secrets — l’action `spawn-twenty-docker-image` démarre un serveur Twenty éphémère directement dans le runner et fournit les détails de connexion. Le secret `GITHUB_TOKEN` est fourni automatiquement par GitHub. + +Pour épingler une version spécifique de Twenty au lieu de `latest`, modifiez la variable d’environnement `TWENTY_VERSION` en haut du workflow. diff --git a/packages/twenty-docs/l/fr/developers/extend/capabilities/apis.mdx b/packages/twenty-docs/l/fr/developers/extend/capabilities/apis.mdx index bf5d5b149e..29d24ddc49 100644 --- a/packages/twenty-docs/l/fr/developers/extend/capabilities/apis.mdx +++ b/packages/twenty-docs/l/fr/developers/extend/capabilities/apis.mdx @@ -17,7 +17,7 @@ Twenty génère des API spécifiquement pour votre modèle de données : * **Documentation personnalisée** : Générée spécifiquement pour le modèle de données de votre espace de travail- Customer Insights -
-+ Customer Insights +
+
+
+2. Cliquez sur **Obtenir une clé Enterprise**
+3. Lorsque vous êtes redirigé vers Stripe, saisissez vos informations de paiement et confirmez
+4. Lorsque votre clé Enterprise s’affiche, collez-la dans la page des paramètres Enterprise et activez la licence de l’organisation
diff --git a/packages/twenty-docs/l/fr/user-guide/billing/how-tos/billing-faq.mdx b/packages/twenty-docs/l/fr/user-guide/billing/how-tos/billing-faq.mdx
index 6eb54f3d4e..4b0df48bd9 100644
--- a/packages/twenty-docs/l/fr/user-guide/billing/how-tos/billing-faq.mdx
+++ b/packages/twenty-docs/l/fr/user-guide/billing/how-tos/billing-faq.mdx
@@ -6,81 +6,83 @@ description: Foire aux questions sur les tarifs et la facturation de Twenty.
## Tarifs
+
**Idéal pour :**
@@ -30,7 +30,7 @@ Affichez les données sous forme de barres horizontales ou verticales.
* Contacts ajoutés par mois
+
**Idéal pour :**
@@ -58,7 +58,7 @@ Affichez les proportions d'un ensemble.
Affichez les tendances au fil du temps.
-
+
**Idéal pour :**
@@ -78,7 +78,7 @@ Affichez les tendances au fil du temps.
Affichez clairement des valeurs clés uniques.
-
+
**Idéal pour :**
@@ -103,7 +103,7 @@ Affichez clairement des valeurs clés uniques.
Intégrez des outils et du contenu externes directement dans votre tableau de bord.
-
+
**Idéal pour :**
@@ -123,7 +123,7 @@ Intégrez des outils et du contenu externes directement dans votre tableau de bo
Ajoutez du texte et du contenu mis en forme directement dans votre tableau de bord.
-
+
**Idéal pour :**
@@ -139,7 +139,7 @@ Ajoutez du texte et du contenu mis en forme directement dans votre tableau de bo
* Édition de type Markdown
+
-2. Click **Get Enterprise Key**
-3. When you are redirected to Stripe, enter your payment details and confirm
-4. When your Enterprise key is displayed, paste it into the Enterprise settings page and activate the Organization license
+2. Fai clic su **Ottieni la chiave Enterprise**
+3. Quando vieni reindirizzato a Stripe, inserisci i dati di pagamento e conferma
+4. Quando viene visualizzata la tua chiave Enterprise, incollala nella pagina delle impostazioni Enterprise e attiva la licenza dell'organizzazione
diff --git a/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx
index a136abf152..8a2f97aecb 100644
--- a/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx
+++ b/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx
@@ -25,15 +25,25 @@ description: Collega i tuoi account email e calendario a Twenty.
6. Configura le impostazioni di sincronizzazione del calendario (visibilità, creazione automatica) → fai clic su **Aggiungi account**
7. Le tue email e gli eventi del calendario inizieranno a sincronizzarsi automaticamente
-### Configurazione SMTP/CalDAV (Altri Fornitori)
+### Configurazione IMAP/SMTP/CalDAV (Altri Fornitori)
Per altri fornitori di email e calendario:
1. Vai a **Impostazioni → Account**
-2. Configura le impostazioni SMTP per l'email
+2. Configura le impostazioni IMAP per sincronizzare le email in arrivo e le impostazioni SMTP per inviare email
3. Configura le impostazioni CalDAV per il calendario
4. Testa la connessione
+
+
+
+
+
+
+
+
+
+
+
+This component renders inside Twenty.
+
+User: {userId}
+Record: {recordId ?? 'No record context'}
+Component: {componentId}
+Hello, {recipientName}!
; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'greeting', + component: Greeting, +}); +``` + +Archive this record?
+Export {selectedRecordIds.length} selected record(s)?
+
;
+
+export default defineFrontComponent({
+ universalIdentifier: '...',
+ name: 'logo',
+ component: Logo,
+});
+```
+
+자세한 내용은 [퍼블릭 에셋 섹션](/l/ko/developers/extend/apps/config/public-assets)을 참조하세요.
+
+## 스타일링
+
+프런트 컴포넌트는 여러 스타일링 방식을 지원합니다. 다음과 같은 방식을 사용할 수 있습니다:
+
+* **인라인 스타일** — `style={{ color: 'red' }}`
+* **Twenty UI 컴포넌트** — `twenty-sdk/ui`에서 임포트(버튼, 태그, 상태, 칩, 아바타 등)
+* **Emotion** — `@emotion/react`를 사용하는 CSS-in-JS
+* **Styled-components** — `styled.div` 패턴
+* **Tailwind CSS** — 유틸리티 클래스
+* React와 호환되는 **모든 CSS-in-JS 라이브러리**
+
+```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`가 사전 제공 모듈로 사용 가능합니다 — 이는 번들되지 않고 서버가 런타임에 해석합니다. + +## 설정 + +스캐폴딩된 앱에는 이미 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` | 앱을 빌드하고 필요하면 타르볼로 패키징 | +| `appDeploy` | 타르볼을 서버로 업로드 | +| `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 dev:typecheck +``` + +이는 `tsc --noEmit`를 실행하고 모든 타입 오류를 보고합니다. + +## GitHub Actions로 CI + +스캐폴더가 `.github/workflows/ci.yml`에 바로 사용할 수 있는 GitHub Actions 워크플로를 생성합니다. `main`으로의 푸시와 풀 리퀘스트마다 통합 테스트를 자동으로 실행합니다. + +워크플로: + +1. 코드를 체크아웃합니다 +2. `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` 액션을 사용해 임시 Twenty 서버를 구동합니다 +3. `yarn install --immutable`로 종속성을 설치합니다 +4. 액션 출력에서 주입된 `TWENTY_API_URL` 및 `TWENTY_API_KEY`로 `yarn test`를 실행합니다 + +```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 }} +``` + +별도의 시크릿을 구성할 필요가 없습니다 — `spawn-twenty-docker-image` 액션이 러너 내에서 일시적인 Twenty 서버를 직접 시작하고 연결 정보를 출력합니다. `GITHUB_TOKEN` 시크릿은 GitHub에서 자동으로 제공됩니다. + +`latest` 대신 특정 Twenty 버전을 고정하려면 워크플로 상단의 `TWENTY_VERSION` 환경 변수를 변경하세요. diff --git a/packages/twenty-docs/l/ko/developers/extend/capabilities/apis.mdx b/packages/twenty-docs/l/ko/developers/extend/capabilities/apis.mdx index e5dc9555a0..7aa80a7d47 100644 --- a/packages/twenty-docs/l/ko/developers/extend/capabilities/apis.mdx +++ b/packages/twenty-docs/l/ko/developers/extend/capabilities/apis.mdx @@ -17,7 +17,7 @@ Twenty는 귀하의 데이터 모델에 맞는 API를 특별히 생성합니다: * **맞춤형 문서**: 작업 공간의 데이터 모델에 맞게 특별히 생성됩니다.- Customer Insights -
-+ Customer Insights +
+
+
+2. **엔터프라이즈 키 받기**를 클릭하세요
+3. Stripe로 리디렉션되면 결제 정보를 입력하고 확인하세요
+4. 엔터프라이즈 키가 표시되면 이를 엔터프라이즈 설정 페이지에 붙여넣고 Organization 라이선스를 활성화하세요
diff --git a/packages/twenty-docs/l/ko/user-guide/billing/how-tos/billing-faq.mdx b/packages/twenty-docs/l/ko/user-guide/billing/how-tos/billing-faq.mdx
index 4dee8e0b9e..dc04abea4e 100644
--- a/packages/twenty-docs/l/ko/user-guide/billing/how-tos/billing-faq.mdx
+++ b/packages/twenty-docs/l/ko/user-guide/billing/how-tos/billing-faq.mdx
@@ -6,81 +6,83 @@ description: Twenty 요금 및 결제에 관한 자주 묻는 질문입니다.
## 가격
+
**적합한 용도:**
@@ -30,7 +30,7 @@ Twenty는 CRM 데이터를 시각화할 수 있는 다양한 위젯 유형을
* 월별 추가된 연락처
+
**적합한 용도:**
@@ -58,7 +58,7 @@ Twenty는 CRM 데이터를 시각화할 수 있는 다양한 위젯 유형을
시간에 따른 추세를 표시합니다.
-
+
**적합한 용도:**
@@ -78,7 +78,7 @@ Twenty는 CRM 데이터를 시각화할 수 있는 다양한 위젯 유형을
단일 핵심 값을 눈에 띄게 표시합니다.
-
+
**적합한 용도:**
@@ -103,7 +103,7 @@ Twenty는 CRM 데이터를 시각화할 수 있는 다양한 위젯 유형을
대시보드에 외부 도구와 콘텐츠를 직접 삽입합니다.
-
+
**적합한 용도:**
@@ -123,7 +123,7 @@ Twenty는 CRM 데이터를 시각화할 수 있는 다양한 위젯 유형을
대시보드에 서식 있는 텍스트와 콘텐츠를 직접 추가하세요.
-
+
**적합한 용도:**
@@ -139,7 +139,7 @@ Twenty는 CRM 데이터를 시각화할 수 있는 다양한 위젯 유형을
* Markdown 스타일 편집