Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 396efd2d96 | |||
| d7d279a4b4 | |||
| 86a9d3832b | |||
| dc298bf548 | |||
| 17ce7ed781 | |||
| c2872f8e7d | |||
| ac02aa3ce6 | |||
| 579714b62f | |||
| 3ea466755a | |||
| 885ab8b444 | |||
| 268543a08e | |||
| c854d28d60 | |||
| d219c83815 | |||
| 223943550c | |||
| 1c7bda8448 | |||
| 391f6c0dab | |||
| 81f10c586f | |||
| 9438b9869c | |||
| ab16ae6995 | |||
| f688db30a9 | |||
| 49d19494b9 | |||
| 5172572d15 | |||
| 16033e9f99 | |||
| b5492cff64 | |||
| 0cb231a407 | |||
| 2c73d47555 | |||
| 5484d0b776 | |||
| 6d5580c6fd | |||
| 691c86a4a7 | |||
| bbc4d50d4f | |||
| 1af7ac5fc4 | |||
| 72ac10fb7a | |||
| 986256f56d | |||
| ade6ed9c32 | |||
| 19c710b87f | |||
| 7991ce7823 | |||
| fd7387928c | |||
| 3733a5a763 | |||
| 0b2f4cdb57 | |||
| 1622c87b7a | |||
| f3e2e00e79 | |||
| 5de5ed2cb4 | |||
| 6eb4c4ca4b | |||
| ae202a1b59 | |||
| 16e3e38b79 | |||
| 4cc3deb937 | |||
| 10b37a6b39 | |||
| e15feda3c3 | |||
| 9f95c4763c | |||
| e6fe48b66d | |||
| 20ac5b7e84 | |||
| 136f362b24 | |||
| 291792f864 | |||
| 9054d3aef6 | |||
| 19dd4d6c1b | |||
| a0c6727a61 | |||
| b002930554 | |||
| aa0ea96582 | |||
| 36dece43c7 | |||
| ac8e0d4217 | |||
| ee3ebd0ca0 | |||
| c11e4ece39 | |||
| 5bbfce7789 | |||
| 887e0283c5 | |||
| 94e019f012 | |||
| c23961fa81 | |||
| 2612145436 | |||
| 4c97642258 | |||
| 08f019e9c9 | |||
| 888fa271f0 | |||
| bb5c64952c | |||
| 6dedb35a1f | |||
| 436333f110 | |||
| ec283b8f2d | |||
| c3b969ab74 | |||
| 287fe90ce9 | |||
| d10c0a6439 | |||
| 176e81cd76 | |||
| 1ce4da5b67 | |||
| ef66d6b337 | |||
| bcca5d0002 | |||
| e0630b8653 | |||
| 61a27984e8 | |||
| fd21d0c6ca | |||
| 8d539f0e49 | |||
| a6cecdbd49 | |||
| 383935d0d9 | |||
| 37908114fc | |||
| 369ae2862f | |||
| 5bde41ebbb | |||
| 8fa3962e1c | |||
| ca00e8dece | |||
| 2263e14394 | |||
| ecf8161d0e | |||
| 3d1c53ec9d | |||
| 2a5aab0c44 | |||
| 8985dfbc5d | |||
| 40abe1e6d0 | |||
| 6c128a35dd | |||
| c0086646fd | |||
| 08b041633a | |||
| 2fccd29ec6 | |||
| 30bdc24bf8 | |||
| d2cf05f4f4 | |||
| 696a202bb9 | |||
| b8374f5531 | |||
| 1109b89cd7 | |||
| 107914b437 | |||
| cb85a1b5a3 | |||
| ef8789fad6 | |||
| ee479001a1 | |||
| 6af7e32c54 | |||
| d246b16063 | |||
| 0b2f435b3e | |||
| 039a04bc2f | |||
| fe1377f18b |
@@ -35,15 +35,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -53,16 +48,10 @@ jobs:
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
ports:
|
||||
- 6379:6379
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:25.8.8
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
env:
|
||||
CLICKHOUSE_PASSWORD: clickhousePassword
|
||||
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
|
||||
@@ -86,6 +75,8 @@ jobs:
|
||||
run: |
|
||||
echo "Attempting to merge main into current branch..."
|
||||
|
||||
git config user.email "ci@twenty.com"
|
||||
git config user.name "CI"
|
||||
git fetch origin main
|
||||
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
@@ -99,8 +90,8 @@ jobs:
|
||||
echo "❌ Merge failed due to conflicts"
|
||||
echo "⚠️ Falling back to comparing current branch against main without merge"
|
||||
|
||||
# Abort the failed merge
|
||||
git merge --abort
|
||||
# Abort the failed merge (may not exist if merge never started)
|
||||
git merge --abort 2>/dev/null || true
|
||||
|
||||
echo "merged=false" >> $GITHUB_OUTPUT
|
||||
echo "BRANCH_STATE=conflicts" >> $GITHUB_ENV
|
||||
@@ -402,11 +393,22 @@ jobs:
|
||||
# Clean up temp directory
|
||||
rm -rf /tmp/current-branch-files
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
- name: Validate downloaded schema files
|
||||
id: validate-schemas
|
||||
run: |
|
||||
valid=true
|
||||
|
||||
for file in main-schema-introspection.json current-schema-introspection.json \
|
||||
main-metadata-schema-introspection.json current-metadata-schema-introspection.json \
|
||||
main-rest-api.json current-rest-api.json \
|
||||
main-rest-metadata-api.json current-rest-metadata-api.json; do
|
||||
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
|
||||
echo "::warning::Invalid or missing schema file: $file"
|
||||
valid=false
|
||||
fi
|
||||
done
|
||||
|
||||
echo "valid=$valid" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install OpenAPI Diff Tool
|
||||
run: |
|
||||
@@ -414,6 +416,7 @@ jobs:
|
||||
echo "Using OpenAPITools/openapi-diff via Docker"
|
||||
|
||||
- name: Generate GraphQL Schema Diff Reports
|
||||
if: steps.validate-schemas.outputs.valid == 'true'
|
||||
run: |
|
||||
echo "=== INSTALLING GRAPHQL INSPECTOR CLI ==="
|
||||
npm install -g @graphql-inspector/cli
|
||||
@@ -424,7 +427,6 @@ jobs:
|
||||
echo "Checking GraphQL schema for changes..."
|
||||
if graphql-inspector diff main-schema-introspection.json current-schema-introspection.json >/dev/null 2>&1; then
|
||||
echo "✅ No changes in GraphQL schema"
|
||||
# Don't create a diff file for no changes
|
||||
else
|
||||
echo "⚠️ Changes detected in GraphQL schema, generating report..."
|
||||
echo "# GraphQL Schema Changes" > graphql-schema-diff.md
|
||||
@@ -442,7 +444,6 @@ jobs:
|
||||
echo "Checking GraphQL metadata schema for changes..."
|
||||
if graphql-inspector diff main-metadata-schema-introspection.json current-metadata-schema-introspection.json >/dev/null 2>&1; then
|
||||
echo "✅ No changes in GraphQL metadata schema"
|
||||
# Don't create a diff file for no changes
|
||||
else
|
||||
echo "⚠️ Changes detected in GraphQL metadata schema, generating report..."
|
||||
echo "# GraphQL Metadata Schema Changes" > graphql-metadata-diff.md
|
||||
@@ -461,6 +462,7 @@ jobs:
|
||||
ls -la *-diff.md 2>/dev/null || echo "No diff files generated (no changes detected)"
|
||||
|
||||
- name: Check REST API Breaking Changes
|
||||
if: steps.validate-schemas.outputs.valid == 'true'
|
||||
run: |
|
||||
echo "=== CHECKING REST API FOR BREAKING CHANGES ==="
|
||||
|
||||
@@ -529,6 +531,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check REST Metadata API Breaking Changes
|
||||
if: steps.validate-schemas.outputs.valid == 'true'
|
||||
run: |
|
||||
echo "=== CHECKING REST METADATA API FOR BREAKING CHANGES ==="
|
||||
|
||||
|
||||
@@ -36,15 +36,10 @@ jobs:
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -54,9 +49,6 @@ jobs:
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
@@ -164,7 +156,7 @@ jobs:
|
||||
SEED_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik'
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty remote add --token $SEED_API_KEY --url http://localhost:3000
|
||||
npx --no-install twenty remote add --api-key $SEED_API_KEY --api-url http://localhost:3000
|
||||
|
||||
- name: Deploy scaffolded app
|
||||
run: |
|
||||
@@ -188,7 +180,7 @@ jobs:
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
EXEC_OUTPUT=$(npx --no-install twenty exec --functionName create-hello-world-company)
|
||||
echo "$EXEC_OUTPUT"
|
||||
echo "$EXEC_OUTPUT" | grep -q "Created company"
|
||||
echo "$EXEC_OUTPUT" | grep -q 'Created company.*Hello World.*with id'
|
||||
|
||||
- name: Run scaffolded app integration test
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
name: CI Front Component Renderer
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
if: github.event_name != 'merge_group'
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-front-component-renderer/**
|
||||
packages/twenty-sdk/**
|
||||
packages/twenty-shared/**
|
||||
!packages/twenty-sdk/package.json
|
||||
renderer-task:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [build, typecheck, lint]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run ${{ matrix.task }}
|
||||
run: npx nx ${{ matrix.task }} twenty-front-component-renderer
|
||||
renderer-sb-build:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build storybook
|
||||
run: npx nx storybook:build twenty-front-component-renderer
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-front-component-renderer
|
||||
path: packages/twenty-front-component-renderer/storybook-static
|
||||
retention-days: 1
|
||||
renderer-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
needs: renderer-sb-build
|
||||
env:
|
||||
STORYBOOK_URL: http://localhost:6008
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-front-component-renderer
|
||||
path: packages/twenty-front-component-renderer/storybook-static
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd packages/twenty-front-component-renderer
|
||||
npx playwright install
|
||||
- name: Serve storybook & run tests
|
||||
run: |
|
||||
npx http-server packages/twenty-front-component-renderer/storybook-static --port 6008 --silent &
|
||||
timeout 30 bash -c 'until curl -sf http://localhost:6008 > /dev/null 2>&1; do sleep 1; done'
|
||||
npx nx storybook:test twenty-front-component-renderer
|
||||
ci-front-component-renderer-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
renderer-task,
|
||||
renderer-sb-build,
|
||||
renderer-sb-test,
|
||||
]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
@@ -25,6 +25,7 @@ jobs:
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-front/**
|
||||
packages/twenty-front-component-renderer/**
|
||||
packages/twenty-ui/**
|
||||
packages/twenty-shared/**
|
||||
packages/twenty-sdk/**
|
||||
@@ -93,7 +94,7 @@ jobs:
|
||||
run: |
|
||||
npx nx build twenty-shared
|
||||
npx nx build twenty-ui
|
||||
npx nx build twenty-sdk
|
||||
npx nx build twenty-front-component-renderer
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -25,15 +25,10 @@ jobs:
|
||||
NODE_OPTIONS: "--max-old-space-size=10240"
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: "true"
|
||||
SPILO_PROVIDER: "local"
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -43,9 +38,6 @@ jobs:
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
ports:
|
||||
- 6379:6379
|
||||
steps:
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
|
||||
task: [lint, typecheck, test:unit, test:integration]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
@@ -41,9 +41,6 @@ jobs:
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Install Playwright
|
||||
if: contains(matrix.task, 'storybook')
|
||||
run: npx playwright install chromium
|
||||
- name: Run ${{ matrix.task }} task
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
@@ -56,15 +53,10 @@ jobs:
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -74,9 +66,6 @@ jobs:
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
|
||||
@@ -83,15 +83,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -101,9 +96,6 @@ jobs:
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
ports:
|
||||
- 6379:6379
|
||||
steps:
|
||||
@@ -231,15 +223,10 @@ jobs:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -249,16 +236,10 @@ jobs:
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
ports:
|
||||
- 6379:6379
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:25.8.8
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
env:
|
||||
CLICKHOUSE_PASSWORD: clickhousePassword
|
||||
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
|
||||
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
- name: Start container
|
||||
run: |
|
||||
docker run -d --name twenty-app-dev \
|
||||
-p 3000:3000 \
|
||||
-p 2020:2020 \
|
||||
twenty-app-dev-ci
|
||||
docker logs twenty-app-dev -f &
|
||||
- name: Wait for server health
|
||||
@@ -129,10 +129,10 @@ jobs:
|
||||
echo "Waiting for twenty-app-dev to become healthy..."
|
||||
count=0
|
||||
while true; do
|
||||
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/healthz 2>/dev/null || echo "000")
|
||||
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2020/healthz 2>/dev/null || echo "000")
|
||||
if [ "$status" = "200" ]; then
|
||||
echo "Server is healthy!"
|
||||
curl -s http://localhost:3000/healthz
|
||||
curl -s http://localhost:2020/healthz
|
||||
break
|
||||
fi
|
||||
|
||||
|
||||
@@ -26,15 +26,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
|
||||
@@ -29,15 +29,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
image: postgres:18
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -47,9 +42,6 @@ jobs:
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
credentials:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
ports:
|
||||
- 6379:6379
|
||||
steps:
|
||||
|
||||
@@ -82,11 +82,11 @@
|
||||
"@sentry/types": "^8",
|
||||
"@storybook-community/storybook-addon-cookie": "^5.0.0",
|
||||
"@storybook/addon-coverage": "^3.0.0",
|
||||
"@storybook/addon-docs": "^10.2.13",
|
||||
"@storybook/addon-links": "^10.2.13",
|
||||
"@storybook/addon-vitest": "^10.2.13",
|
||||
"@storybook/addon-docs": "^10.3.3",
|
||||
"@storybook/addon-links": "^10.3.3",
|
||||
"@storybook/addon-vitest": "^10.3.3",
|
||||
"@storybook/icons": "^2.0.1",
|
||||
"@storybook/react-vite": "^10.2.13",
|
||||
"@storybook/react-vite": "^10.3.3",
|
||||
"@storybook/test-runner": "^0.24.2",
|
||||
"@swc-node/register": "^1.11.1",
|
||||
"@swc/cli": "^0.7.10",
|
||||
@@ -154,9 +154,9 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"source-map-support": "^0.5.20",
|
||||
"storybook": "^10.2.13",
|
||||
"storybook": "^10.3.3",
|
||||
"storybook-addon-mock-date": "2.0.0",
|
||||
"storybook-addon-pseudo-states": "^10.2.13",
|
||||
"storybook-addon-pseudo-states": "^10.3.3",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.2.3",
|
||||
@@ -180,6 +180,7 @@
|
||||
"graphql": "16.8.1",
|
||||
"type-fest": "4.10.1",
|
||||
"typescript": "5.9.2",
|
||||
"nodemailer": "8.0.4",
|
||||
"graphql-redis-subscriptions/ioredis": "^5.6.0",
|
||||
"@lingui/core": "5.1.2",
|
||||
"@types/qs": "6.9.16",
|
||||
@@ -208,6 +209,7 @@
|
||||
"packages/twenty-e2e-testing",
|
||||
"packages/twenty-shared",
|
||||
"packages/twenty-sdk",
|
||||
"packages/twenty-front-component-renderer",
|
||||
"packages/twenty-client-sdk",
|
||||
"packages/twenty-apps",
|
||||
"packages/twenty-cli",
|
||||
|
||||
@@ -12,164 +12,49 @@
|
||||
|
||||
</div>
|
||||
|
||||
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a ready‑to‑run project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
|
||||
|
||||
- Zero‑config project bootstrap
|
||||
- Preconfigured scripts for auth, dev mode (watch & sync), uninstall, and function management
|
||||
- Strong TypeScript support and typed client generation
|
||||
|
||||
## Documentation
|
||||
|
||||
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 24+ (recommended) and Yarn 4
|
||||
- Docker (for the local Twenty dev server)
|
||||
The official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). Sets up a ready-to-run project with [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# Scaffold a new app — the CLI will offer to start a local Twenty server
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
|
||||
# The scaffolder can automatically:
|
||||
# 1. Start a local Twenty server (Docker)
|
||||
# 2. Open the browser to log in (tim@apple.dev / tim@apple.dev)
|
||||
# 3. Authenticate your app via OAuth
|
||||
|
||||
# Or do it manually:
|
||||
yarn twenty server start # Start local Twenty server
|
||||
yarn twenty remote add http://localhost:2020 --as local # Authenticate via OAuth
|
||||
|
||||
# Start dev mode: watches, builds, and syncs local changes to your workspace
|
||||
# (also auto-generates typed CoreApiClient — MetadataApiClient ships pre-built — both available via `twenty-client-sdk`)
|
||||
yarn twenty dev
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty logs
|
||||
|
||||
# Execute a function with a JSON payload
|
||||
yarn twenty exec -n my-function -p '{"key": "value"}'
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty exec --preInstall
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty exec --postInstall
|
||||
|
||||
# Build the app for distribution
|
||||
yarn twenty build
|
||||
|
||||
# Publish the app to npm or directly to a Twenty server
|
||||
yarn twenty publish
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
```
|
||||
|
||||
The scaffolder will:
|
||||
|
||||
1. Create a new project with TypeScript, linting, and a preconfigured `twenty` CLI
|
||||
2. Optionally start a local Twenty server (Docker)
|
||||
3. Open the browser for OAuth authentication
|
||||
4. Scaffold example entities and an integration test
|
||||
|
||||
## Scaffolding modes
|
||||
|
||||
Control which example files are included when creating a new app:
|
||||
| Flag | Behavior |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| `--minimal` | **(default)** Creates only core files (`application-config.ts`, `default-role.ts`, pre/post-install functions) |
|
||||
| `--exhaustive` | Creates all example entities |
|
||||
|
||||
| Flag | Behavior |
|
||||
| ------------------ | ----------------------------------------------------------------------- |
|
||||
| `-e, --exhaustive` | **(default)** Creates all example files |
|
||||
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
|
||||
Other flags:
|
||||
|
||||
```bash
|
||||
# Default: all examples included
|
||||
npx create-twenty-app@latest my-app
|
||||
- `--name <name>` — set the app name (skips the prompt)
|
||||
- `--display-name <displayName>` — set the display name (skips the prompt)
|
||||
- `--description <description>` — set the description (skips the prompt)
|
||||
- `--skip-local-instance` — skip the local server setup prompt
|
||||
|
||||
# Minimal: only core files
|
||||
npx create-twenty-app@latest my-app -m
|
||||
```
|
||||
## Documentation
|
||||
|
||||
## What gets scaffolded
|
||||
Full documentation is available at **[docs.twenty.com/developers/extend/apps](https://docs.twenty.com/developers/extend/apps/getting-started)**:
|
||||
|
||||
**Core files (always created):**
|
||||
|
||||
- `application-config.ts` — Application metadata configuration
|
||||
- `roles/default-role.ts` — Default role for logic functions
|
||||
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
|
||||
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
|
||||
- TypeScript configuration, Oxlint, package.json, .gitignore
|
||||
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
|
||||
|
||||
**Example files (controlled by scaffolding mode):**
|
||||
|
||||
- `objects/example-object.ts` — Example custom object with a text field
|
||||
- `fields/example-field.ts` — Example standalone field extending the example object
|
||||
- `logic-functions/hello-world.ts` — Example logic function with HTTP trigger
|
||||
- `front-components/hello-world.tsx` — Example front component
|
||||
- `views/example-view.ts` — Example saved view for the example object
|
||||
- `navigation-menu-items/example-navigation-menu-item.ts` — Example sidebar navigation link
|
||||
- `skills/example-skill.ts` — Example AI agent skill definition
|
||||
- `__tests__/app-install.integration-test.ts` — Integration test that builds, installs, and verifies the app (includes `vitest.config.ts`, `tsconfig.spec.json`, and a setup file)
|
||||
|
||||
## Local server
|
||||
|
||||
The scaffolder can start a local Twenty dev server for you (all-in-one Docker image with PostgreSQL, Redis, server, and worker). You can also manage it manually:
|
||||
|
||||
```bash
|
||||
yarn twenty server start # Start (pulls image if needed)
|
||||
yarn twenty server status # Check if it's healthy
|
||||
yarn twenty server logs # Stream logs
|
||||
yarn twenty server stop # Stop (data is preserved)
|
||||
yarn twenty server reset # Wipe all data and start fresh
|
||||
```
|
||||
|
||||
The server is pre-seeded with a workspace and user (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
## Next steps
|
||||
|
||||
- Run `yarn twenty help` to see all available commands.
|
||||
- Use `yarn twenty remote add <url>` to authenticate with your Twenty workspace via OAuth.
|
||||
- Explore the generated project and add your first entity with `yarn twenty add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
|
||||
- Use `yarn twenty dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
|
||||
- `CoreApiClient` is auto-generated by `yarn twenty dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient } from 'twenty-client-sdk/core'` and `import { MetadataApiClient } from 'twenty-client-sdk/metadata'`.
|
||||
|
||||
## Build and publish your application
|
||||
|
||||
Once your app is ready, build and publish it using the CLI:
|
||||
|
||||
```bash
|
||||
# Build the app (output goes to .twenty/output/)
|
||||
yarn twenty build
|
||||
|
||||
# Build and create a tarball (.tgz) for distribution
|
||||
yarn twenty build --tarball
|
||||
|
||||
# Publish to npm (requires npm login)
|
||||
yarn twenty publish
|
||||
|
||||
# Publish with a dist-tag (e.g. beta, next)
|
||||
yarn twenty publish --tag beta
|
||||
|
||||
# Deploy directly to a Twenty server (builds, uploads, and installs in one step)
|
||||
yarn twenty deploy
|
||||
```
|
||||
|
||||
### Publish to the Twenty marketplace
|
||||
|
||||
You can also contribute your application to the curated marketplace:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
cd twenty
|
||||
git checkout -b feature/my-awesome-app
|
||||
```
|
||||
|
||||
- Copy your app folder into `twenty/packages/twenty-apps`.
|
||||
- Commit your changes and open a pull request on https://github.com/twentyhq/twenty
|
||||
|
||||
Our team reviews contributions for quality, security, and reusability before merging.
|
||||
- [Getting Started](https://docs.twenty.com/developers/extend/apps/getting-started) — step-by-step setup, project structure, server management, CI
|
||||
- [Building Apps](https://docs.twenty.com/developers/extend/apps/building) — entity definitions, API clients, testing
|
||||
- [Publishing](https://docs.twenty.com/developers/extend/apps/publishing) — deploy, npm publish, marketplace
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Server not starting: check Docker is running (`docker info`), then try `yarn twenty server logs`.
|
||||
- Auth not working: make sure you're logged in to Twenty in the browser first, then run `yarn twenty remote add <url>`.
|
||||
- Auth not working: make sure you are logged in to Twenty in the browser, then run `yarn twenty remote add`.
|
||||
- Types not generated: ensure `yarn twenty dev` is running — it auto-generates the typed client.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.8.0-canary.5",
|
||||
"version": "0.8.0-canary.8",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
|
||||
@@ -13,10 +13,10 @@ const program = new Command(packageJson.name)
|
||||
'Output the current version of create-twenty-app.',
|
||||
)
|
||||
.argument('[directory]')
|
||||
.option('-e, --exhaustive', 'Create all example entities (default)')
|
||||
.option('-e, --exhaustive', 'Create all example entities')
|
||||
.option(
|
||||
'-m, --minimal',
|
||||
'Create only core entities (application-config and default-role)',
|
||||
'Create only core entities (application-config and default-role) (default)',
|
||||
)
|
||||
.option('-n, --name <name>', 'Application name (skips prompt)')
|
||||
.option(
|
||||
@@ -69,7 +69,9 @@ const program = new Command(packageJson.name)
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
|
||||
const mode: ScaffoldingMode = options?.exhaustive
|
||||
? 'exhaustive'
|
||||
: 'minimal';
|
||||
|
||||
await new CreateAppCommand().execute({
|
||||
directory,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
|
||||
|
||||
## UUID requirement
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as path from 'path';
|
||||
import { basename } from 'path';
|
||||
import {
|
||||
authLoginOAuth,
|
||||
detectLocalServer,
|
||||
serverStart,
|
||||
type ServerStartResult,
|
||||
} from 'twenty-sdk/cli';
|
||||
@@ -38,7 +39,7 @@ export class CreateAppCommand {
|
||||
|
||||
try {
|
||||
const exampleOptions = this.resolveExampleOptions(
|
||||
options.mode ?? 'exhaustive',
|
||||
options.mode ?? 'minimal',
|
||||
);
|
||||
|
||||
await this.validateDirectory(appDirectory);
|
||||
@@ -62,15 +63,19 @@ export class CreateAppCommand {
|
||||
let serverResult: ServerStartResult | undefined;
|
||||
|
||||
if (!options.skipLocalInstance) {
|
||||
const startResult = await serverStart({
|
||||
onProgress: (message: string) => console.log(chalk.gray(message)),
|
||||
});
|
||||
const shouldStartServer = await this.shouldStartServer();
|
||||
|
||||
if (startResult.success) {
|
||||
serverResult = startResult.data;
|
||||
await this.connectToLocal(serverResult.url);
|
||||
} else {
|
||||
console.log(chalk.yellow(`\n${startResult.error.message}`));
|
||||
if (shouldStartServer) {
|
||||
const startResult = await serverStart({
|
||||
onProgress: (message: string) => console.log(chalk.gray(message)),
|
||||
});
|
||||
|
||||
if (startResult.success) {
|
||||
serverResult = startResult.data;
|
||||
await this.promptConnectToLocal(serverResult.url);
|
||||
} else {
|
||||
console.log(chalk.yellow(`\n${startResult.error.message}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +163,7 @@ export class CreateAppCommand {
|
||||
includeExampleNavigationMenuItem: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleAgent: false,
|
||||
includeExampleIntegrationTest: false,
|
||||
includeExampleIntegrationTest: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,7 +206,46 @@ export class CreateAppCommand {
|
||||
);
|
||||
}
|
||||
|
||||
private async connectToLocal(serverUrl: string): Promise<void> {
|
||||
private async shouldStartServer(): Promise<boolean> {
|
||||
const existingServerUrl = await detectLocalServer();
|
||||
|
||||
if (existingServerUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { startDocker } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'startDocker',
|
||||
message:
|
||||
'No running Twenty instance found. Would you like to start one using Docker?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
return startDocker;
|
||||
}
|
||||
|
||||
private async promptConnectToLocal(serverUrl: string): Promise<void> {
|
||||
const { shouldAuthenticate } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'shouldAuthenticate',
|
||||
message: `Would you like to authenticate to the local Twenty instance (${serverUrl})?`,
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!shouldAuthenticate) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'Authentication skipped. Run `yarn twenty remote add` manually.',
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await authLoginOAuth({
|
||||
apiUrl: serverUrl,
|
||||
@@ -211,14 +255,14 @@ export class CreateAppCommand {
|
||||
if (!result.success) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
|
||||
'Authentication failed. Run `yarn twenty remote add` manually.',
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
|
||||
'Authentication failed. Run `yarn twenty remote add` manually.',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -236,7 +280,7 @@ export class CreateAppCommand {
|
||||
if (!serverResult) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'- yarn twenty remote add --local # Authenticate with Twenty',
|
||||
'- yarn twenty remote add # Authenticate with Twenty',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -425,8 +425,12 @@ const handler = async (): Promise<{ message: string }> => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!createCompany?.id || !createCompany?.name) {
|
||||
throw new Error('Failed to create company: missing id or name in response');
|
||||
}
|
||||
|
||||
return {
|
||||
message: \`Created company "\${createCompany?.name}" with id \${createCompany?.id}\`,
|
||||
message: \`Created company "\${createCompany.name}" with id \${createCompany.id}\`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -794,6 +798,7 @@ const createPackageJson = async ({
|
||||
npm: 'please-use-yarn',
|
||||
yarn: '>=4.0.2',
|
||||
},
|
||||
keywords: ['twenty-app'],
|
||||
packageManager: 'yarn@4.9.2',
|
||||
scripts,
|
||||
devDependencies,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
|
||||
|
||||
## UUID requirement
|
||||
- All generated UUIDs must be valid UUID v4.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,7 @@
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"remote:add": "twenty remote add --local",
|
||||
"remote:status": "twenty remote status",
|
||||
"remote:switch": "twenty remote switch",
|
||||
"remote:list": "twenty remote list",
|
||||
"remote:remove": "twenty remote remove",
|
||||
"dev": "twenty dev",
|
||||
"add": "twenty add",
|
||||
"logs": "twenty logs",
|
||||
"exec": "twenty exec",
|
||||
"uninstall": "twenty uninstall",
|
||||
"help": "twenty help",
|
||||
"twenty": "twenty",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,7 @@
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"remote:add": "twenty remote add --local",
|
||||
"remote:status": "twenty remote status",
|
||||
"remote:switch": "twenty remote switch",
|
||||
"remote:list": "twenty remote list",
|
||||
"remote:remove": "twenty remote remove",
|
||||
"dev": "twenty dev",
|
||||
"add": "twenty add",
|
||||
"logs": "twenty logs",
|
||||
"exec": "twenty exec",
|
||||
"uninstall": "twenty uninstall",
|
||||
"help": "twenty help",
|
||||
"twenty": "twenty",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -9,22 +9,13 @@
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"remote:add": "twenty remote add --local",
|
||||
"remote:status": "twenty remote status",
|
||||
"remote:switch": "twenty remote switch",
|
||||
"remote:list": "twenty remote list",
|
||||
"remote:remove": "twenty remote remove",
|
||||
"dev": "twenty dev",
|
||||
"add": "twenty add",
|
||||
"logs": "twenty logs",
|
||||
"exec": "twenty exec",
|
||||
"uninstall": "twenty uninstall",
|
||||
"help": "twenty help",
|
||||
"twenty": "twenty",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
"twenty-sdk": "latest",
|
||||
"twenty-client-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
|
||||
// Field on existing company object
|
||||
export default defineField({
|
||||
universalIdentifier: 'f922fdb8-10a9-4f11-a1d0-992a779f6dff',
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
type: FieldType.BOOLEAN,
|
||||
name: 'canReceivePostcards',
|
||||
label: 'Can Receive Postcards',
|
||||
description: 'Whether the company can receive postcards',
|
||||
icon: 'IconMailbox',
|
||||
defaultValue: false,
|
||||
});
|
||||
@@ -6,6 +6,9 @@ export const RECIPIENT_UNIVERSAL_IDENTIFIER =
|
||||
export const RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'd2a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
export const RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: RECIPIENT_UNIVERSAL_IDENTIFIER,
|
||||
nameSingular: 'recipient',
|
||||
@@ -23,7 +26,7 @@ export default defineObject({
|
||||
icon: 'IconMail',
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
universalIdentifier: RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.ADDRESS,
|
||||
label: 'Mailing Address',
|
||||
name: 'mailingAddress',
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
import {
|
||||
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
|
||||
SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
} from '../objects/post-card-recipient.object';
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
|
||||
export const ALL_POST_CARD_RECIPIENTS_VIEW_ID =
|
||||
'b1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
export const POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'fd959c6f-3465-4a3a-b7ad-3f4004fffc9a';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: ALL_POST_CARD_RECIPIENTS_VIEW_ID,
|
||||
name: 'All Post Card Recipients',
|
||||
@@ -17,7 +20,8 @@ export default defineView({
|
||||
position: 2,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
universalIdentifier:
|
||||
POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
import {
|
||||
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
RECIPIENT_UNIVERSAL_IDENTIFIER,
|
||||
} from '../objects/recipient.object';
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
|
||||
export const ALL_RECIPIENTS_VIEW_ID = 'b1a2b3c4-0002-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
@@ -23,5 +24,13 @@ export default defineView({
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
fieldMetadataUniversalIdentifier:
|
||||
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
|
||||
|
||||
## UUID requirement
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
|
||||
First, authenticate to your workspace:
|
||||
|
||||
```bash
|
||||
yarn twenty remote add http://localhost:2020 --as local
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
||||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add http://localhost:2020 --as local # Authenticate with Twenty
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
|
||||
yarn twenty remote status # Check auth status
|
||||
yarn twenty remote switch # Switch default remote
|
||||
yarn twenty remote list # List all configured remotes
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
"@types/react": "^18.2.0",
|
||||
"oxlint": "^0.16.0",
|
||||
"react": "^18.2.0",
|
||||
"twenty-client-sdk": "portal:../../twenty-client-sdk",
|
||||
"twenty-sdk": "portal:../../twenty-sdk",
|
||||
"twenty-client-sdk": "0.8.0-canary.5",
|
||||
"twenty-sdk": "0.8.0-canary.5",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"vitest": "^3.1.1"
|
||||
|
||||
@@ -16,8 +16,12 @@ const handler = async (): Promise<{ message: string }> => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!createCompany?.id || !createCompany?.name) {
|
||||
throw new Error('Failed to create company: missing id or name in response');
|
||||
}
|
||||
|
||||
return {
|
||||
message: `Created company "${createCompany?.name}" with id ${createCompany?.id}`,
|
||||
message: `Created company "${createCompany.name}" with id ${createCompany.id}`,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ export default defineObject({
|
||||
labelPlural: 'Example items',
|
||||
description: 'A sample custom object',
|
||||
icon: 'IconBox',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
|
||||
@@ -5,13 +5,13 @@ __metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"@alcalzone/ansi-tokenize@npm:^0.1.3":
|
||||
version: 0.1.3
|
||||
resolution: "@alcalzone/ansi-tokenize@npm:0.1.3"
|
||||
"@alcalzone/ansi-tokenize@npm:^0.2.4":
|
||||
version: 0.2.5
|
||||
resolution: "@alcalzone/ansi-tokenize@npm:0.2.5"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^6.2.1"
|
||||
is-fullwidth-code-point: "npm:^4.0.0"
|
||||
checksum: 10c0/b88c5708271bb64ce132fc80dac8d5b87fc1699bf3abfdf10ecae40dbb56ab82460818f5746ecdd9870a00be9854e039d5cb3a121b808d0ff6f5bc7d0146cb38
|
||||
is-fullwidth-code-point: "npm:^5.0.0"
|
||||
checksum: 10c0/dd8622288426b5b7dbf8b68d51d4b930cea591d0e3b9dbc3f523131464d78ac922c165fcb7ba5307f133d35dbcaa0e6648a1c3fb6a0dc2725546d6f867b70af4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2655,7 +2655,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-escapes@npm:^7.0.0":
|
||||
"ansi-escapes@npm:^7.3.0":
|
||||
version: 7.3.0
|
||||
resolution: "ansi-escapes@npm:7.3.0"
|
||||
dependencies:
|
||||
@@ -2717,7 +2717,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.2.1":
|
||||
"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3":
|
||||
version: 6.2.3
|
||||
resolution: "ansi-styles@npm:6.2.3"
|
||||
checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868
|
||||
@@ -2948,7 +2948,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^5.3.0":
|
||||
"chalk@npm:^5.3.0, chalk@npm:^5.6.0":
|
||||
version: 5.6.2
|
||||
resolution: "chalk@npm:5.6.2"
|
||||
checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976
|
||||
@@ -3020,13 +3020,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-truncate@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "cli-truncate@npm:4.0.0"
|
||||
"cli-truncate@npm:^5.1.1":
|
||||
version: 5.2.0
|
||||
resolution: "cli-truncate@npm:5.2.0"
|
||||
dependencies:
|
||||
slice-ansi: "npm:^5.0.0"
|
||||
string-width: "npm:^7.0.0"
|
||||
checksum: 10c0/d7f0b73e3d9b88cb496e6c086df7410b541b56a43d18ade6a573c9c18bd001b1c3fba1ad578f741a4218fdc794d042385f8ac02c25e1c295a2d8b9f3cb86eb4c
|
||||
slice-ansi: "npm:^8.0.0"
|
||||
string-width: "npm:^8.2.0"
|
||||
checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3306,7 +3306,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-toolkit@npm:^1.22.0":
|
||||
"es-toolkit@npm:^1.39.10":
|
||||
version: 1.45.1
|
||||
resolution: "es-toolkit@npm:1.45.1"
|
||||
dependenciesMeta:
|
||||
@@ -3727,7 +3727,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1":
|
||||
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "get-east-asian-width@npm:1.5.0"
|
||||
checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167
|
||||
@@ -3906,8 +3906,8 @@ __metadata:
|
||||
"@types/react": "npm:^18.2.0"
|
||||
oxlint: "npm:^0.16.0"
|
||||
react: "npm:^18.2.0"
|
||||
twenty-client-sdk: "portal:../../twenty-client-sdk"
|
||||
twenty-sdk: "portal:../../twenty-sdk"
|
||||
twenty-client-sdk: "npm:0.8.0-canary.5"
|
||||
twenty-sdk: "npm:0.8.0-canary.5"
|
||||
typescript: "npm:^5.9.3"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
vitest: "npm:^3.1.1"
|
||||
@@ -4030,44 +4030,45 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ink@npm:^5.1.1":
|
||||
version: 5.2.1
|
||||
resolution: "ink@npm:5.2.1"
|
||||
"ink@npm:^6.8.0":
|
||||
version: 6.8.0
|
||||
resolution: "ink@npm:6.8.0"
|
||||
dependencies:
|
||||
"@alcalzone/ansi-tokenize": "npm:^0.1.3"
|
||||
ansi-escapes: "npm:^7.0.0"
|
||||
"@alcalzone/ansi-tokenize": "npm:^0.2.4"
|
||||
ansi-escapes: "npm:^7.3.0"
|
||||
ansi-styles: "npm:^6.2.1"
|
||||
auto-bind: "npm:^5.0.1"
|
||||
chalk: "npm:^5.3.0"
|
||||
chalk: "npm:^5.6.0"
|
||||
cli-boxes: "npm:^3.0.0"
|
||||
cli-cursor: "npm:^4.0.0"
|
||||
cli-truncate: "npm:^4.0.0"
|
||||
cli-truncate: "npm:^5.1.1"
|
||||
code-excerpt: "npm:^4.0.0"
|
||||
es-toolkit: "npm:^1.22.0"
|
||||
es-toolkit: "npm:^1.39.10"
|
||||
indent-string: "npm:^5.0.0"
|
||||
is-in-ci: "npm:^1.0.0"
|
||||
is-in-ci: "npm:^2.0.0"
|
||||
patch-console: "npm:^2.0.0"
|
||||
react-reconciler: "npm:^0.29.0"
|
||||
scheduler: "npm:^0.23.0"
|
||||
react-reconciler: "npm:^0.33.0"
|
||||
scheduler: "npm:^0.27.0"
|
||||
signal-exit: "npm:^3.0.7"
|
||||
slice-ansi: "npm:^7.1.0"
|
||||
slice-ansi: "npm:^8.0.0"
|
||||
stack-utils: "npm:^2.0.6"
|
||||
string-width: "npm:^7.2.0"
|
||||
type-fest: "npm:^4.27.0"
|
||||
widest-line: "npm:^5.0.0"
|
||||
string-width: "npm:^8.1.1"
|
||||
terminal-size: "npm:^4.0.1"
|
||||
type-fest: "npm:^5.4.1"
|
||||
widest-line: "npm:^6.0.0"
|
||||
wrap-ansi: "npm:^9.0.0"
|
||||
ws: "npm:^8.18.0"
|
||||
yoga-layout: "npm:~3.2.1"
|
||||
peerDependencies:
|
||||
"@types/react": ">=18.0.0"
|
||||
react: ">=18.0.0"
|
||||
react-devtools-core: ^4.19.1
|
||||
"@types/react": ">=19.0.0"
|
||||
react: ">=19.0.0"
|
||||
react-devtools-core: ">=6.1.2"
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
react-devtools-core:
|
||||
optional: true
|
||||
checksum: 10c0/0e2607712783353fbe99438ca4220db3d5874c7ae1a07e2b232f7539489b13a9db929b3046eedeccf318a3ffa222a572287dbe2307c848b8e7f6ec4bba39e550
|
||||
checksum: 10c0/50500e547fdf6a1f1d836d6befbd4770e3ab649ef0be1884500a6da411fb68a90e22dd7dcc9c404911d30e9f87506b3b9d8e997c6c6ceac85ee054b4dadefaff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4140,14 +4141,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-fullwidth-code-point@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "is-fullwidth-code-point@npm:4.0.0"
|
||||
checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-fullwidth-code-point@npm:^5.0.0":
|
||||
"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "is-fullwidth-code-point@npm:5.1.0"
|
||||
dependencies:
|
||||
@@ -4165,12 +4159,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-in-ci@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "is-in-ci@npm:1.0.0"
|
||||
"is-in-ci@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "is-in-ci@npm:2.0.0"
|
||||
bin:
|
||||
is-in-ci: cli.js
|
||||
checksum: 10c0/98f9cec4c35aece4cf731abf35ebf28359a9b0324fac810da05b842515d9ccb33b8999c1d9a679f0362e1a4df3292065c38d7f86327b1387fa667bc0150f4898
|
||||
checksum: 10c0/1e1d1056939a681e8206035de5ad84e0404556eaa7622bb55f0f1868b9788bff3df427bc0b1ed5a172623154a90fcb1e759a230817cd73d09435543ae3c71feb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5014,15 +5008,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-dom@npm:^18.2.0":
|
||||
version: 18.3.1
|
||||
resolution: "react-dom@npm:18.3.1"
|
||||
"react-dom@npm:^19.0.0":
|
||||
version: 19.2.4
|
||||
resolution: "react-dom@npm:19.2.4"
|
||||
dependencies:
|
||||
loose-envify: "npm:^1.1.0"
|
||||
scheduler: "npm:^0.23.2"
|
||||
scheduler: "npm:^0.27.0"
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85
|
||||
react: ^19.2.4
|
||||
checksum: 10c0/f0c63f1794dedb154136d4d0f59af00b41907f4859571c155940296808f4b94bf9c0c20633db75b5b2112ec13d8d7dd4f9bf57362ed48782f317b11d05a44f35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5033,15 +5026,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-reconciler@npm:^0.29.0":
|
||||
version: 0.29.2
|
||||
resolution: "react-reconciler@npm:0.29.2"
|
||||
"react-reconciler@npm:^0.33.0":
|
||||
version: 0.33.0
|
||||
resolution: "react-reconciler@npm:0.33.0"
|
||||
dependencies:
|
||||
loose-envify: "npm:^1.1.0"
|
||||
scheduler: "npm:^0.23.2"
|
||||
scheduler: "npm:^0.27.0"
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
checksum: 10c0/94f48ddc348a974256cf13c859f5a94efdb0cd72e04c51b1a4d5c72a8b960ccd35df2196057ee6a4cbcb26145e12b01e3f9ba3b183fddb901414db36a07cbf43
|
||||
react: ^19.2.0
|
||||
checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5054,6 +5046,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:^19.0.0":
|
||||
version: 19.2.4
|
||||
resolution: "react@npm:19.2.4"
|
||||
checksum: 10c0/cd2c9ff67a720799cc3b38a516009986f7fc4cb8d3e15716c6211cf098d1357ee3e348ab05ad0600042bbb0fd888530ba92e329198c92eafa0994f5213396596
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"readdirp@npm:^4.0.1":
|
||||
version: 4.1.2
|
||||
resolution: "readdirp@npm:4.1.2"
|
||||
@@ -5298,12 +5297,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"scheduler@npm:^0.23.0, scheduler@npm:^0.23.2":
|
||||
version: 0.23.2
|
||||
resolution: "scheduler@npm:0.23.2"
|
||||
dependencies:
|
||||
loose-envify: "npm:^1.1.0"
|
||||
checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78
|
||||
"scheduler@npm:^0.27.0":
|
||||
version: 0.27.0
|
||||
resolution: "scheduler@npm:0.27.0"
|
||||
checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5406,23 +5403,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slice-ansi@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "slice-ansi@npm:5.0.0"
|
||||
"slice-ansi@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "slice-ansi@npm:8.0.0"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^6.0.0"
|
||||
is-fullwidth-code-point: "npm:^4.0.0"
|
||||
checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slice-ansi@npm:^7.1.0":
|
||||
version: 7.1.2
|
||||
resolution: "slice-ansi@npm:7.1.2"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^6.2.1"
|
||||
is-fullwidth-code-point: "npm:^5.0.0"
|
||||
checksum: 10c0/36742f2eb0c03e2e81a38ed14d13a64f7b732fe38c3faf96cce0599788a345011e840db35f1430ca606ea3f8db2abeb92a8d25c2753a819e3babaa10c2e289a2
|
||||
ansi-styles: "npm:^6.2.3"
|
||||
is-fullwidth-code-point: "npm:^5.1.0"
|
||||
checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5532,7 +5519,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^7.0.0, string-width@npm:^7.2.0":
|
||||
"string-width@npm:^7.0.0":
|
||||
version: 7.2.0
|
||||
resolution: "string-width@npm:7.2.0"
|
||||
dependencies:
|
||||
@@ -5543,6 +5530,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^8.1.0, string-width@npm:^8.1.1, string-width@npm:^8.2.0":
|
||||
version: 8.2.0
|
||||
resolution: "string-width@npm:8.2.0"
|
||||
dependencies:
|
||||
get-east-asian-width: "npm:^1.5.0"
|
||||
strip-ansi: "npm:^7.1.2"
|
||||
checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "strip-ansi@npm:3.0.1"
|
||||
@@ -5570,7 +5567,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^7.1.0":
|
||||
"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2":
|
||||
version: 7.2.0
|
||||
resolution: "strip-ansi@npm:7.2.0"
|
||||
dependencies:
|
||||
@@ -5640,6 +5637,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tagged-tag@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "tagged-tag@npm:1.0.0"
|
||||
checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:^7.5.4":
|
||||
version: 7.5.11
|
||||
resolution: "tar@npm:7.5.11"
|
||||
@@ -5653,6 +5657,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terminal-size@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "terminal-size@npm:4.0.1"
|
||||
checksum: 10c0/89afd9d816dd9dbfe4499da9aeea70491bbde4ff4592226a9c8ac71074a7580afead6a78e95ecc35f6d42e09087b55ffcb1019302cd55e0cc957b6ce5c4847e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinybench@npm:^2.9.0":
|
||||
version: 2.9.0
|
||||
resolution: "tinybench@npm:2.9.0"
|
||||
@@ -5751,20 +5762,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-client-sdk@portal:../../twenty-client-sdk::locator=hello-world%40workspace%3A.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "twenty-client-sdk@portal:../../twenty-client-sdk::locator=hello-world%40workspace%3A."
|
||||
"twenty-client-sdk@npm:0.8.0-canary.5":
|
||||
version: 0.8.0-canary.5
|
||||
resolution: "twenty-client-sdk@npm:0.8.0-canary.5"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
esbuild: "npm:^0.25.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
checksum: 10c0/754b92b732c8e03c9779f6557324710afe4c07f7e6efbe766bd8ff3b6dd8a00c0d9f3fecce2098d68ef9556cafa722b2189e490473e420fdb9cf30c56beb3619
|
||||
languageName: node
|
||||
linkType: soft
|
||||
linkType: hard
|
||||
|
||||
"twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A."
|
||||
"twenty-sdk@npm:0.8.0-canary.5":
|
||||
version: 0.8.0-canary.5
|
||||
resolution: "twenty-sdk@npm:0.8.0-canary.5"
|
||||
dependencies:
|
||||
"@chakra-ui/react": "npm:^3.33.0"
|
||||
"@emotion/react": "npm:^11.14.0"
|
||||
@@ -5782,13 +5794,14 @@ __metadata:
|
||||
esbuild: "npm:^0.25.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
graphql-sse: "npm:^2.5.4"
|
||||
ink: "npm:^5.1.1"
|
||||
ink: "npm:^6.8.0"
|
||||
inquirer: "npm:^10.0.0"
|
||||
jsonc-parser: "npm:^3.2.0"
|
||||
preact: "npm:^10.28.3"
|
||||
react: "npm:^18.2.0"
|
||||
react-dom: "npm:^18.2.0"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
twenty-client-sdk: "npm:0.8.0-canary.5"
|
||||
typescript: "npm:^5.9.2"
|
||||
uuid: "npm:^13.0.0"
|
||||
vite: "npm:^7.0.0"
|
||||
@@ -5796,8 +5809,9 @@ __metadata:
|
||||
zod: "npm:^4.1.11"
|
||||
bin:
|
||||
twenty: dist/cli.cjs
|
||||
checksum: 10c0/47d0970c88f59c092e8eca34dca38bf88d9d52678997349068ca8a8e5cebed2ea71dbd4e8b7299f40b99b5419d4cecd53b31f821b65959267e8d97eb91d2ddde
|
||||
languageName: node
|
||||
linkType: soft
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^0.21.3":
|
||||
version: 0.21.3
|
||||
@@ -5806,10 +5820,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^4.27.0":
|
||||
version: 4.41.0
|
||||
resolution: "type-fest@npm:4.41.0"
|
||||
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
|
||||
"type-fest@npm:^5.4.1":
|
||||
version: 5.5.0
|
||||
resolution: "type-fest@npm:5.5.0"
|
||||
dependencies:
|
||||
tagged-tag: "npm:^1.0.0"
|
||||
checksum: 10c0/60bf79a8df45abf99490e3204eceb5cf7f915413f8a69fb578c75cab37ddcb7d29ee21f185f0e1617323ac0b2a441e001b8dc691e220d0b087e9c29ea205538c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6123,12 +6139,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"widest-line@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "widest-line@npm:5.0.0"
|
||||
"widest-line@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "widest-line@npm:6.0.0"
|
||||
dependencies:
|
||||
string-width: "npm:^7.0.0"
|
||||
checksum: 10c0/6bd6cca8cda502ef50e05353fd25de0df8c704ffc43ada7e0a9cf9a5d4f4e12520485d80e0b77cec8a21f6c3909042fcf732aa9281e5dbb98cc9384a138b2578
|
||||
string-width: "npm:^8.1.0"
|
||||
checksum: 10c0/735f1fdcd97fe765a07bb8b5e73c020bed8e53ab34e83ce0ef01693ba3c914d9e7977fe5f5facf0d0b670297a82dd5e376d3efa0896860dfcdaf7cd6924c0fb7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
|
||||
First, authenticate to your workspace:
|
||||
|
||||
```bash
|
||||
yarn twenty remote add http://localhost:2020 --as local
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
||||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add http://localhost:2020 --as local # Authenticate with Twenty
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
|
||||
yarn twenty remote status # Check auth status
|
||||
yarn twenty remote switch # Switch default remote
|
||||
yarn twenty remote list # List all configured remotes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-client-sdk",
|
||||
"version": "0.8.0-canary.5",
|
||||
"version": "0.8.0-canary.8",
|
||||
"sideEffects": false,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -38,9 +38,6 @@ type ApplicationRegistration {
|
||||
id: UUID!
|
||||
universalIdentifier: String!
|
||||
name: String!
|
||||
description: String
|
||||
logoUrl: String
|
||||
author: String
|
||||
oAuthClientId: String!
|
||||
oAuthRedirectUris: [String!]!
|
||||
oAuthScopes: [String!]!
|
||||
@@ -48,8 +45,6 @@ type ApplicationRegistration {
|
||||
sourceType: ApplicationRegistrationSourceType!
|
||||
sourcePackage: String
|
||||
latestAvailableVersion: String
|
||||
websiteUrl: String
|
||||
termsUrl: String
|
||||
isListed: Boolean!
|
||||
isFeatured: Boolean!
|
||||
createdAt: DateTime!
|
||||
@@ -1725,25 +1720,28 @@ enum FeatureFlagKey {
|
||||
IS_UNIQUE_INDEXES_ENABLED
|
||||
IS_JSON_FILTER_ENABLED
|
||||
IS_AI_ENABLED
|
||||
IS_COMMAND_MENU_ITEM_ENABLED
|
||||
IS_MARKETPLACE_ENABLED
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
|
||||
IS_PUBLIC_DOMAIN_ENABLED
|
||||
IS_EMAILING_DOMAIN_ENABLED
|
||||
IS_JUNCTION_RELATIONS_ENABLED
|
||||
IS_COMMAND_MENU_ITEM_ENABLED
|
||||
IS_NAVIGATION_MENU_ITEM_ENABLED
|
||||
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED
|
||||
IS_DRAFT_EMAIL_ENABLED
|
||||
IS_USAGE_ANALYTICS_ENABLED
|
||||
IS_RICH_TEXT_V1_MIGRATED
|
||||
IS_DIRECT_GRAPHQL_EXECUTION_ENABLED
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED
|
||||
IS_GRAPHQL_QUERY_TIMING_ENABLED
|
||||
IS_RECORD_TABLE_WIDGET_ENABLED
|
||||
IS_DATASOURCE_MIGRATED
|
||||
}
|
||||
|
||||
type ClientConfigMaintenanceMode {
|
||||
startAt: DateTime!
|
||||
endAt: DateTime!
|
||||
link: String
|
||||
}
|
||||
|
||||
type ClientConfig {
|
||||
appVersion: String
|
||||
authProviders: AuthProviders!
|
||||
@@ -1772,6 +1770,8 @@ type ClientConfig {
|
||||
calendarBookingPageId: String
|
||||
isCloudflareIntegrationEnabled: Boolean!
|
||||
isClickHouseConfigured: Boolean!
|
||||
isWorkspaceSchemaDDLLocked: Boolean!
|
||||
maintenance: ClientConfigMaintenanceMode
|
||||
}
|
||||
|
||||
type UsageBreakdownItem {
|
||||
@@ -1968,6 +1968,12 @@ type AdminPanelHealthServiceData {
|
||||
queues: [AdminPanelWorkerQueueHealth!]
|
||||
}
|
||||
|
||||
type MaintenanceMode {
|
||||
startAt: DateTime!
|
||||
endAt: DateTime!
|
||||
link: String
|
||||
}
|
||||
|
||||
type ModelsDevModelSuggestion {
|
||||
modelId: String!
|
||||
name: String!
|
||||
@@ -2274,6 +2280,33 @@ type FieldConnection {
|
||||
edges: [FieldEdge!]!
|
||||
}
|
||||
|
||||
type AuthToken {
|
||||
token: String!
|
||||
expiresAt: DateTime!
|
||||
}
|
||||
|
||||
type ApplicationTokenPair {
|
||||
applicationAccessToken: AuthToken!
|
||||
applicationRefreshToken: AuthToken!
|
||||
}
|
||||
|
||||
type FrontComponent {
|
||||
id: UUID!
|
||||
name: String!
|
||||
description: String
|
||||
sourceComponentPath: String!
|
||||
builtComponentPath: String!
|
||||
componentName: String!
|
||||
builtComponentChecksum: String!
|
||||
universalIdentifier: UUID
|
||||
applicationId: UUID!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
isHeadless: Boolean!
|
||||
usesSdkClient: Boolean!
|
||||
applicationTokenPair: ApplicationTokenPair
|
||||
}
|
||||
|
||||
type LogicFunctionLogs {
|
||||
"""Execution Logs"""
|
||||
logs: String!
|
||||
@@ -2296,11 +2329,6 @@ type AuthorizeApp {
|
||||
redirectUrl: String!
|
||||
}
|
||||
|
||||
type AuthToken {
|
||||
token: String!
|
||||
expiresAt: DateTime!
|
||||
}
|
||||
|
||||
type AuthTokenPair {
|
||||
accessOrWorkspaceAgnosticToken: AuthToken!
|
||||
refreshToken: AuthToken!
|
||||
@@ -2409,11 +2437,6 @@ type WorkspaceMigration {
|
||||
actions: JSON!
|
||||
}
|
||||
|
||||
type ApplicationTokenPair {
|
||||
applicationAccessToken: AuthToken!
|
||||
applicationRefreshToken: AuthToken!
|
||||
}
|
||||
|
||||
type File {
|
||||
id: UUID!
|
||||
path: String!
|
||||
@@ -2421,91 +2444,30 @@ type File {
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type MarketplaceAppField {
|
||||
name: String!
|
||||
type: String!
|
||||
label: String!
|
||||
description: String
|
||||
icon: String
|
||||
objectUniversalIdentifier: String
|
||||
universalIdentifier: String
|
||||
}
|
||||
|
||||
type MarketplaceAppObject {
|
||||
universalIdentifier: String!
|
||||
nameSingular: String!
|
||||
namePlural: String!
|
||||
labelSingular: String!
|
||||
labelPlural: String!
|
||||
description: String
|
||||
icon: String
|
||||
fields: [MarketplaceAppField!]!
|
||||
}
|
||||
|
||||
type MarketplaceAppLogicFunction {
|
||||
name: String!
|
||||
description: String
|
||||
timeoutSeconds: Int
|
||||
}
|
||||
|
||||
type MarketplaceAppFrontComponent {
|
||||
name: String!
|
||||
description: String
|
||||
}
|
||||
|
||||
type MarketplaceAppRoleObjectPermission {
|
||||
objectUniversalIdentifier: String!
|
||||
canReadObjectRecords: Boolean
|
||||
canUpdateObjectRecords: Boolean
|
||||
canSoftDeleteObjectRecords: Boolean
|
||||
canDestroyObjectRecords: Boolean
|
||||
}
|
||||
|
||||
type MarketplaceAppRoleFieldPermission {
|
||||
objectUniversalIdentifier: String!
|
||||
fieldUniversalIdentifier: String!
|
||||
canReadFieldValue: Boolean
|
||||
canUpdateFieldValue: Boolean
|
||||
}
|
||||
|
||||
type MarketplaceAppDefaultRole {
|
||||
id: String!
|
||||
label: String!
|
||||
description: String
|
||||
canReadAllObjectRecords: Boolean!
|
||||
canUpdateAllObjectRecords: Boolean!
|
||||
canSoftDeleteAllObjectRecords: Boolean!
|
||||
canDestroyAllObjectRecords: Boolean!
|
||||
canUpdateAllSettings: Boolean!
|
||||
canAccessAllTools: Boolean!
|
||||
objectPermissions: [MarketplaceAppRoleObjectPermission!]!
|
||||
fieldPermissions: [MarketplaceAppRoleFieldPermission!]!
|
||||
permissionFlags: [String!]!
|
||||
}
|
||||
|
||||
type MarketplaceApp {
|
||||
id: String!
|
||||
name: String!
|
||||
description: String!
|
||||
icon: String!
|
||||
version: String!
|
||||
author: String!
|
||||
category: String!
|
||||
logo: String
|
||||
screenshots: [String!]!
|
||||
aboutDescription: String!
|
||||
providers: [String!]!
|
||||
websiteUrl: String
|
||||
termsUrl: String
|
||||
objects: [MarketplaceAppObject!]!
|
||||
fields: [MarketplaceAppField!]!
|
||||
logicFunctions: [MarketplaceAppLogicFunction!]!
|
||||
frontComponents: [MarketplaceAppFrontComponent!]!
|
||||
defaultRole: MarketplaceAppDefaultRole
|
||||
sourcePackage: String
|
||||
isFeatured: Boolean!
|
||||
}
|
||||
|
||||
type MarketplaceAppDetail {
|
||||
universalIdentifier: String!
|
||||
id: String!
|
||||
name: String!
|
||||
sourceType: ApplicationRegistrationSourceType!
|
||||
sourcePackage: String
|
||||
latestAvailableVersion: String
|
||||
isListed: Boolean!
|
||||
isFeatured: Boolean!
|
||||
manifest: JSON
|
||||
}
|
||||
|
||||
type PublicDomain {
|
||||
id: UUID!
|
||||
domain: String!
|
||||
@@ -2594,23 +2556,6 @@ type PostgresCredentials {
|
||||
workspaceId: UUID!
|
||||
}
|
||||
|
||||
type FrontComponent {
|
||||
id: UUID!
|
||||
name: String!
|
||||
description: String
|
||||
sourceComponentPath: String!
|
||||
builtComponentPath: String!
|
||||
componentName: String!
|
||||
builtComponentChecksum: String!
|
||||
universalIdentifier: UUID
|
||||
applicationId: UUID!
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
isHeadless: Boolean!
|
||||
usesSdkClient: Boolean!
|
||||
applicationTokenPair: ApplicationTokenPair
|
||||
}
|
||||
|
||||
type CommandMenuItem {
|
||||
id: UUID!
|
||||
workflowVersionId: UUID
|
||||
@@ -2635,20 +2580,15 @@ enum EngineComponentKey {
|
||||
NAVIGATE_TO_NEXT_RECORD
|
||||
NAVIGATE_TO_PREVIOUS_RECORD
|
||||
CREATE_NEW_RECORD
|
||||
DELETE_SINGLE_RECORD
|
||||
DELETE_MULTIPLE_RECORDS
|
||||
RESTORE_SINGLE_RECORD
|
||||
RESTORE_MULTIPLE_RECORDS
|
||||
DESTROY_SINGLE_RECORD
|
||||
DESTROY_MULTIPLE_RECORDS
|
||||
DELETE_RECORDS
|
||||
RESTORE_RECORDS
|
||||
DESTROY_RECORDS
|
||||
ADD_TO_FAVORITES
|
||||
REMOVE_FROM_FAVORITES
|
||||
EXPORT_NOTE_TO_PDF
|
||||
EXPORT_FROM_RECORD_INDEX
|
||||
EXPORT_FROM_RECORD_SHOW
|
||||
EXPORT_RECORDS
|
||||
UPDATE_MULTIPLE_RECORDS
|
||||
MERGE_MULTIPLE_RECORDS
|
||||
EXPORT_MULTIPLE_RECORDS
|
||||
IMPORT_RECORDS
|
||||
EXPORT_VIEW
|
||||
SEE_DELETED_RECORDS
|
||||
@@ -2691,6 +2631,15 @@ enum EngineComponentKey {
|
||||
VIEW_PREVIOUS_AI_CHATS
|
||||
TRIGGER_WORKFLOW_VERSION
|
||||
FRONT_COMPONENT_RENDERER
|
||||
DELETE_SINGLE_RECORD
|
||||
DELETE_MULTIPLE_RECORDS
|
||||
RESTORE_SINGLE_RECORD
|
||||
RESTORE_MULTIPLE_RECORDS
|
||||
DESTROY_SINGLE_RECORD
|
||||
DESTROY_MULTIPLE_RECORDS
|
||||
EXPORT_FROM_RECORD_INDEX
|
||||
EXPORT_FROM_RECORD_SHOW
|
||||
EXPORT_MULTIPLE_RECORDS
|
||||
}
|
||||
|
||||
enum CommandMenuItemAvailabilityType {
|
||||
@@ -2855,10 +2804,12 @@ type AgentChatThread {
|
||||
type AgentMessage {
|
||||
id: UUID!
|
||||
threadId: UUID!
|
||||
turnId: UUID!
|
||||
turnId: UUID
|
||||
agentId: UUID
|
||||
role: String!
|
||||
status: String!
|
||||
parts: [AgentMessagePart!]!
|
||||
processedAt: DateTime
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
@@ -2873,6 +2824,22 @@ type AISystemPromptPreview {
|
||||
estimatedTokenCount: Int!
|
||||
}
|
||||
|
||||
type ChatStreamCatchupChunks {
|
||||
chunks: [JSON!]!
|
||||
maxSeq: Int!
|
||||
}
|
||||
|
||||
type SendChatMessageResult {
|
||||
messageId: String!
|
||||
queued: Boolean!
|
||||
streamId: String
|
||||
}
|
||||
|
||||
type AgentChatEvent {
|
||||
threadId: String!
|
||||
event: JSON!
|
||||
}
|
||||
|
||||
type AgentChatThreadEdge {
|
||||
"""The node containing the AgentChatThread"""
|
||||
node: AgentChatThread!
|
||||
@@ -3220,6 +3187,7 @@ type Query {
|
||||
minimalMetadata: MinimalMetadata!
|
||||
chatThread(id: UUID!): AgentChatThread!
|
||||
chatMessages(threadId: UUID!): [AgentMessage!]!
|
||||
chatStreamCatchupChunks(threadId: UUID!): ChatStreamCatchupChunks!
|
||||
getAISystemPromptPreview: AISystemPromptPreview!
|
||||
skills: [Skill!]!
|
||||
skill(id: UUID!): Skill
|
||||
@@ -3249,6 +3217,7 @@ type Query {
|
||||
findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats!
|
||||
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariable!]!
|
||||
applicationRegistrationTarballUrl(id: String!): String
|
||||
getApplicationShareLink(id: String!): String!
|
||||
currentUser: User!
|
||||
currentWorkspace: Workspace!
|
||||
getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData!
|
||||
@@ -3269,12 +3238,13 @@ type Query {
|
||||
getModelsDevProviders: [ModelsDevProviderSuggestion!]!
|
||||
getModelsDevSuggestions(providerType: String!): [ModelsDevModelSuggestion!]!
|
||||
getAdminAiUsageByWorkspace(periodStart: DateTime, periodEnd: DateTime): [UsageBreakdownItem!]!
|
||||
getMaintenanceMode: MaintenanceMode
|
||||
getUsageAnalytics(input: UsageAnalyticsInput): UsageAnalytics!
|
||||
getPostgresCredentials: PostgresCredentials
|
||||
findManyPublicDomains: [PublicDomain!]!
|
||||
getEmailingDomains: [EmailingDomain!]!
|
||||
findManyMarketplaceApps: [MarketplaceApp!]!
|
||||
findOneMarketplaceApp(universalIdentifier: String!): MarketplaceApp!
|
||||
findMarketplaceAppDetail(universalIdentifier: String!): MarketplaceAppDetail!
|
||||
findManyApplications: [Application!]!
|
||||
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
|
||||
}
|
||||
@@ -3519,6 +3489,9 @@ type Mutation {
|
||||
updateWebhook(input: UpdateWebhookInput!): Webhook!
|
||||
deleteWebhook(id: UUID!): Webhook!
|
||||
createChatThread: AgentChatThread!
|
||||
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String): SendChatMessageResult!
|
||||
stopAgentChatStream(threadId: UUID!): Boolean!
|
||||
deleteQueuedChatMessage(messageId: UUID!): Boolean!
|
||||
createSkill(input: CreateSkillInput!): Skill!
|
||||
updateSkill(input: UpdateSkillInput!): Skill!
|
||||
deleteSkill(id: UUID!): Skill!
|
||||
@@ -3586,6 +3559,8 @@ type Mutation {
|
||||
removeAiProvider(providerName: String!): Boolean!
|
||||
addModelToProvider(providerName: String!, modelConfig: JSON!): Boolean!
|
||||
removeModelFromProvider(providerName: String!, modelName: String!): Boolean!
|
||||
setMaintenanceMode(startAt: DateTime!, endAt: DateTime!, link: String): Boolean!
|
||||
clearMaintenanceMode: Boolean!
|
||||
enablePostgresProxy: PostgresCredentials!
|
||||
disablePostgresProxy: PostgresCredentials!
|
||||
createPublicDomain(domain: String!): PublicDomain!
|
||||
@@ -3596,6 +3571,7 @@ type Mutation {
|
||||
verifyEmailingDomain(id: String!): EmailingDomain!
|
||||
createOneAppToken(input: CreateOneAppTokenInput!): AppToken!
|
||||
installMarketplaceApp(universalIdentifier: String!, version: String): Boolean!
|
||||
syncMarketplaceCatalog: Boolean!
|
||||
installApplication(appRegistrationId: String!, version: String): Boolean!
|
||||
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
|
||||
uninstallApplication(universalIdentifier: String!): Boolean!
|
||||
@@ -3832,8 +3808,13 @@ input UpsertFieldsWidgetGroupInput {
|
||||
}
|
||||
|
||||
input UpsertFieldsWidgetFieldInput {
|
||||
"""The id of the view field"""
|
||||
viewFieldId: UUID!
|
||||
"""The id of the view field. Required if fieldMetadataId is not provided."""
|
||||
viewFieldId: UUID
|
||||
|
||||
"""
|
||||
The id of the field metadata. Used to create a new view field when viewFieldId is not provided.
|
||||
"""
|
||||
fieldMetadataId: UUID
|
||||
isVisible: Boolean!
|
||||
position: Float!
|
||||
}
|
||||
@@ -4441,14 +4422,9 @@ input GetAuthorizationUrlForSSOInput {
|
||||
|
||||
input CreateApplicationRegistrationInput {
|
||||
name: String!
|
||||
description: String
|
||||
logoUrl: String
|
||||
author: String
|
||||
universalIdentifier: String
|
||||
oAuthRedirectUris: [String!]
|
||||
oAuthScopes: [String!]
|
||||
websiteUrl: String
|
||||
termsUrl: String
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationInput {
|
||||
@@ -4458,13 +4434,8 @@ input UpdateApplicationRegistrationInput {
|
||||
|
||||
input UpdateApplicationRegistrationPayload {
|
||||
name: String
|
||||
description: String
|
||||
logoUrl: String
|
||||
author: String
|
||||
oAuthRedirectUris: [String!]
|
||||
oAuthScopes: [String!]
|
||||
websiteUrl: String
|
||||
termsUrl: String
|
||||
isListed: Boolean
|
||||
}
|
||||
|
||||
@@ -4612,6 +4583,7 @@ enum FileFolder {
|
||||
type Subscription {
|
||||
onEventSubscription(eventStreamId: String!): EventSubscription
|
||||
logicFunctionLogs(input: LogicFunctionLogsInput!): LogicFunctionLogs!
|
||||
onAgentChatEvent(threadId: UUID!): AgentChatEvent!
|
||||
}
|
||||
|
||||
input LogicFunctionLogsInput {
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"lib": ["es2020", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
oneshot
|
||||
@@ -0,0 +1 @@
|
||||
/bin/sh /etc/s6-overlay/scripts/register-crons.sh
|
||||
@@ -1,10 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
step_start() { echo "==> START $1"; }
|
||||
step_done() { echo "==> DONE"; }
|
||||
|
||||
# Wait for PostgreSQL to be ready (timeout after 60s)
|
||||
echo "Waiting for PostgreSQL..."
|
||||
step_start "Waiting for PostgreSQL"
|
||||
TRIES=0
|
||||
until su-exec postgres pg_isready -h localhost; do
|
||||
until su-exec postgres pg_isready -h localhost > /dev/null 2>&1; do
|
||||
TRIES=$((TRIES + 1))
|
||||
if [ "$TRIES" -ge 120 ]; then
|
||||
echo "ERROR: PostgreSQL did not become ready within 60s"
|
||||
@@ -12,7 +15,7 @@ until su-exec postgres pg_isready -h localhost; do
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
echo "PostgreSQL is ready."
|
||||
step_done
|
||||
|
||||
# Create role if it doesn't exist
|
||||
su-exec postgres psql -h localhost -tc \
|
||||
@@ -31,26 +34,36 @@ has_schema=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
|
||||
"SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'core')")
|
||||
|
||||
if [ "$has_schema" = "f" ]; then
|
||||
echo "Database appears to be empty, running initial setup..."
|
||||
step_start "Running initial database setup"
|
||||
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/database/scripts/setup-db.js
|
||||
step_done
|
||||
fi
|
||||
|
||||
# Always run migrations (idempotent — skips already-applied ones)
|
||||
step_start "Running migrations"
|
||||
yarn database:migrate:prod --force
|
||||
step_done
|
||||
|
||||
step_start "Flushing cache"
|
||||
yarn command:prod cache:flush
|
||||
step_done
|
||||
|
||||
step_start "Running upgrade"
|
||||
yarn command:prod upgrade
|
||||
step_done
|
||||
|
||||
step_start "Flushing cache"
|
||||
yarn command:prod cache:flush
|
||||
step_done
|
||||
|
||||
# Only seed on first boot — check if the dev workspace already exists
|
||||
has_workspace=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
|
||||
"SELECT EXISTS (SELECT 1 FROM core.workspace WHERE id = '20202020-1c25-4d02-bf25-6aeccf7ea419')")
|
||||
|
||||
if [ "$has_workspace" = "f" ]; then
|
||||
echo "Seeding app dev data..."
|
||||
step_start "Seeding workspace data"
|
||||
yarn command:prod workspace:seed:dev --light || true
|
||||
else
|
||||
echo "Dev workspace already seeded, skipping."
|
||||
step_done
|
||||
fi
|
||||
|
||||
echo "Database initialization complete."
|
||||
echo "==> START Database ready"
|
||||
echo "==> DONE"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "==> START Registering cron jobs"
|
||||
|
||||
cd /app/packages/twenty-server
|
||||
yarn command:prod cron:register:all --dev-mode
|
||||
|
||||
echo "==> DONE"
|
||||
@@ -16,6 +16,7 @@ COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
|
||||
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
|
||||
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
|
||||
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/
|
||||
COPY ./packages/twenty-front-component-renderer/package.json /app/packages/twenty-front-component-renderer/
|
||||
COPY ./packages/twenty-sdk/package.json /app/packages/twenty-sdk/
|
||||
COPY ./packages/twenty-client-sdk/package.json /app/packages/twenty-client-sdk/
|
||||
|
||||
@@ -51,6 +52,7 @@ FROM common-deps AS twenty-front-build
|
||||
ARG REACT_APP_SERVER_BASE_URL
|
||||
|
||||
COPY ./packages/twenty-front /app/packages/twenty-front
|
||||
COPY ./packages/twenty-front-component-renderer /app/packages/twenty-front-component-renderer
|
||||
COPY ./packages/twenty-ui /app/packages/twenty-ui
|
||||
COPY ./packages/twenty-shared /app/packages/twenty-shared
|
||||
COPY ./packages/twenty-sdk /app/packages/twenty-sdk
|
||||
@@ -193,9 +195,9 @@ RUN find /app/packages/twenty-server/dist -name '*.js.map' -delete
|
||||
# s6 service definitions
|
||||
COPY packages/twenty-docker/twenty-app-dev/rootfs/ /
|
||||
|
||||
RUN mkdir -p /data/postgres /data/redis /app/.local-storage \
|
||||
RUN mkdir -p /data/postgres /data/redis /app/packages/twenty-server/.local-storage \
|
||||
&& chown -R postgres:postgres /data/postgres \
|
||||
&& chown 1000:1000 /data/redis /app/.local-storage
|
||||
&& chown 1000:1000 /data/redis /app/packages/twenty-server/.local-storage
|
||||
|
||||
ARG REACT_APP_SERVER_BASE_URL
|
||||
ARG APP_VERSION=0.0.0
|
||||
@@ -209,14 +211,14 @@ ENV PG_DATABASE_URL=postgres://twenty:twenty@localhost:5432/default \
|
||||
REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL \
|
||||
APP_VERSION=$APP_VERSION \
|
||||
NODE_ENV=development \
|
||||
NODE_PORT=3000 \
|
||||
NODE_PORT=2020 \
|
||||
DISABLE_DB_MIGRATIONS=true \
|
||||
DISABLE_CRON_JOBS_REGISTRATION=true \
|
||||
IS_BILLING_ENABLED=false \
|
||||
SIGN_IN_PREFILLED=true
|
||||
|
||||
EXPOSE 3000
|
||||
VOLUME ["/data/postgres", "/app/.local-storage"]
|
||||
EXPOSE 2020
|
||||
VOLUME ["/data/postgres", "/app/packages/twenty-server/.local-storage"]
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
||||
LABEL org.opencontainers.image.description="All-in-one Twenty image for local development and SDK usage. Includes PostgreSQL, Redis, server, and worker."
|
||||
|
||||
@@ -4,83 +4,142 @@ description: Create your first Twenty app in minutes.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Apps are currently in alpha testing. The feature is functional but still evolving.
|
||||
Apps are currently in alpha. The feature works but is still evolving.
|
||||
</Warning>
|
||||
|
||||
Apps let you extend Twenty with custom objects, fields, logic functions, AI skills, and UI components — all managed as code.
|
||||
|
||||
**What you can do today:**
|
||||
- Define custom objects and fields as code (managed data model)
|
||||
- Build logic functions with custom triggers (HTTP routes, cron, database events)
|
||||
- Define skills for AI agents
|
||||
- Build front components that render inside Twenty's UI
|
||||
- Deploy the same app across multiple workspaces
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 24+ and Yarn 4
|
||||
- Docker (for the local Twenty dev server)
|
||||
Before you begin, make sure the following is installed on your machine:
|
||||
|
||||
## Getting Started
|
||||
- **Node.js 24+** — [Download here](https://nodejs.org/)
|
||||
- **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable`
|
||||
- **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running.
|
||||
|
||||
Create a new app using the official scaffolder, then authenticate and start developing:
|
||||
## Step 1: Scaffold your app
|
||||
|
||||
Open a terminal and run:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults.
|
||||
|
||||
This creates a new folder called `my-twenty-app` with everything you need.
|
||||
|
||||
<Note>
|
||||
The scaffolder supports these flags:
|
||||
|
||||
- `--minimal` — scaffold only the essential files, no examples (default)
|
||||
- `--exhaustive` — scaffold all example entities
|
||||
- `--name <name>` — set the app name (skips the prompt)
|
||||
- `--display-name <displayName>` — set the display name (skips the prompt)
|
||||
- `--description <description>` — set the description (skips the prompt)
|
||||
- `--skip-local-instance` — skip the local server setup prompt
|
||||
</Note>
|
||||
|
||||
## Step 2: Set up a local Twenty instance
|
||||
|
||||
The scaffolder will ask:
|
||||
|
||||
> **Would you like to set up a local Twenty instance?**
|
||||
|
||||
- **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue.
|
||||
- **Type `no`** — Choose this if you already have a Twenty server running locally.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Should start local instance?" />
|
||||
</div>
|
||||
|
||||
## Step 3: Sign in to your workspace
|
||||
|
||||
Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account:
|
||||
|
||||
- **Email:** `tim@apple.dev`
|
||||
- **Password:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty login screen" />
|
||||
</div>
|
||||
|
||||
## Step 4: Authorize the app
|
||||
|
||||
After you sign in, you will see an authorization screen. This lets your app interact with your workspace.
|
||||
|
||||
Click **Authorize** to continue.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI authorization screen" />
|
||||
</div>
|
||||
|
||||
Once authorized, your terminal will confirm that everything is set up.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App scaffolded successfully" />
|
||||
</div>
|
||||
|
||||
## Step 5: Start developing
|
||||
|
||||
Go into your new app folder and start the development server:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
The scaffolder supports two modes for controlling which example files are included:
|
||||
This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal.
|
||||
|
||||
For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
From here you can:
|
||||
<Warning>
|
||||
Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/developers/extend/apps/publishing) for details.
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Dev mode terminal output" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## Step 6: See your app in Twenty
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Your Apps list showing My twenty app" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Application registration details" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
Click **View installed app** to see the installed app. The **About** tab shows the current version and management options:
|
||||
|
||||
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installed app — About tab" />
|
||||
</div>
|
||||
|
||||
## Project structure (scaffolded)
|
||||
Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents:
|
||||
|
||||
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installed app — Content tab" />
|
||||
</div>
|
||||
|
||||
- Copies a minimal base application into `my-twenty-app/`
|
||||
- Adds a local `twenty-sdk` dependency and Yarn 4 configuration
|
||||
- Creates config files and scripts wired to the `twenty` CLI
|
||||
- Generates core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode
|
||||
You are all set! Edit any file in `src/` and the changes will be picked up automatically.
|
||||
|
||||
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
|
||||
Head over to [Building Apps](/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more.
|
||||
|
||||
---
|
||||
|
||||
## Project structure
|
||||
|
||||
The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -93,123 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`).
|
||||
By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above.
|
||||
|
||||
At a high level:
|
||||
### Key files
|
||||
|
||||
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus a `twenty` script that delegates to the local `twenty` CLI. Run `yarn twenty help` to list all available commands.
|
||||
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
|
||||
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
|
||||
- **.nvmrc**: Pins the Node.js version expected by the project.
|
||||
- **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
|
||||
- **README.md**: A short README in the app root with basic instructions.
|
||||
- **public/**: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
|
||||
- **src/**: The main place where you define your application-as-code
|
||||
| File / Folder | Purpose |
|
||||
|---|---|
|
||||
| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. |
|
||||
| `src/application-config.ts` | **Required.** The main configuration file for your app. |
|
||||
| `src/roles/` | Defines roles that control what your logic functions can access. |
|
||||
| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. |
|
||||
| `src/front-components/` | React components that render inside Twenty's UI. |
|
||||
| `src/objects/` | Custom object definitions to extend your data model. |
|
||||
| `src/fields/` | Custom fields added to existing objects. |
|
||||
| `src/views/` | Saved view configurations. |
|
||||
| `src/navigation-menu-items/` | Custom links in the sidebar navigation. |
|
||||
| `src/skills/` | Skills that extend Twenty's AI agents. |
|
||||
| `src/agents/` | AI agents with custom prompts. |
|
||||
| `src/page-layouts/` | Custom page layouts for record views. |
|
||||
| `src/__tests__/` | Integration tests (setup + example test). |
|
||||
| `public/` | Static assets (images, fonts) served with your app. |
|
||||
|
||||
### Entity detection
|
||||
## Managing remotes
|
||||
|
||||
The SDK detects entities by parsing your TypeScript files for **`export default define<Entity>({...})`** calls. Each entity type has a corresponding helper function exported from `twenty-sdk`:
|
||||
|
||||
| Helper function | Entity type |
|
||||
|-----------------|-------------|
|
||||
| `defineObject` | Custom object definitions |
|
||||
| `defineLogicFunction` | Logic function definitions |
|
||||
| `definePreInstallLogicFunction` | Pre-install logic function (runs before installation) |
|
||||
| `definePostInstallLogicFunction` | Post-install logic function (runs after installation) |
|
||||
| `defineFrontComponent` | Front component definitions |
|
||||
| `defineRole` | Role definitions |
|
||||
| `defineField` | Field extensions for existing objects |
|
||||
| `defineView` | Saved view definitions |
|
||||
| `defineNavigationMenuItem` | Navigation menu item definitions |
|
||||
| `defineSkill` | AI agent skill definitions |
|
||||
|
||||
<Note>
|
||||
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
|
||||
</Note>
|
||||
|
||||
Example of a detected entity:
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Later commands will add more files and folders:
|
||||
|
||||
- `yarn twenty dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
- `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
|
||||
|
||||
## Authentication
|
||||
|
||||
The first time you run `yarn twenty auth:login`, you'll be prompted for:
|
||||
|
||||
- API URL (defaults to http://localhost:3000 or your current workspace profile)
|
||||
- API key
|
||||
|
||||
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch between them.
|
||||
|
||||
### Managing workspaces
|
||||
A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Once you've switched workspaces with `yarn twenty auth:switch`, all subsequent commands will use that workspace by default. You can still override it temporarily with `--workspace <name>`.
|
||||
Your credentials are stored in `~/.twenty/config.json`.
|
||||
|
||||
## Local development server (`yarn twenty server`)
|
||||
|
||||
The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually.
|
||||
|
||||
### Starting the server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning.
|
||||
|
||||
Two Docker volumes are created to persist data between restarts:
|
||||
- `twenty-app-dev-data` — PostgreSQL database
|
||||
- `twenty-app-dev-storage` — file storage
|
||||
|
||||
If port 2020 is already in use, you can start on a different port:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly.
|
||||
|
||||
Once started, the server is automatically registered as the `local` remote in your CLI config.
|
||||
|
||||
### Checking server status
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### Viewing server logs
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
Streams the container logs. Use `--lines` to control how many recent lines to show:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### Stopping the server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off.
|
||||
|
||||
### Resetting the server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance.
|
||||
|
||||
<Note>
|
||||
The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started.
|
||||
</Note>
|
||||
|
||||
### Command reference
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `yarn twenty server start` | Start the local server (pulls image if needed) |
|
||||
| `yarn twenty server start --port 3030` | Start on a custom port |
|
||||
| `yarn twenty server stop` | Stop the server (preserves data) |
|
||||
| `yarn twenty server status` | Show server status, URL, and credentials |
|
||||
| `yarn twenty server logs` | Stream server logs |
|
||||
| `yarn twenty server logs --lines 100` | Show the last 100 log lines |
|
||||
| `yarn twenty server reset` | Delete all data and start fresh |
|
||||
|
||||
|
||||
## CI with GitHub Actions
|
||||
|
||||
The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests.
|
||||
|
||||
The workflow:
|
||||
|
||||
1. Checks out your code
|
||||
2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action
|
||||
3. Installs dependencies with `yarn install --immutable`
|
||||
4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs
|
||||
|
||||
```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 }}
|
||||
```
|
||||
|
||||
You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub.
|
||||
|
||||
To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow.
|
||||
|
||||
## Manual setup (without the scaffolder)
|
||||
|
||||
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire a single script in your package.json:
|
||||
If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps.
|
||||
|
||||
**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Then add a `twenty` script:
|
||||
**2. Add a `twenty` script to your `package.json`:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -219,25 +393,19 @@ Then add a `twenty` script:
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty dev`, `yarn twenty help`, etc.
|
||||
You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands.
|
||||
|
||||
## How to use a local Twenty instance
|
||||
|
||||
If you're already running a Twenty instance locally (e.g. via `npx nx start twenty-server`), you can connect to it instead of using Docker:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version.
|
||||
</Note>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Authentication errors: run `yarn twenty auth:login` and ensure your API key has the required permissions.
|
||||
- Cannot connect to server: verify the API URL and that the Twenty server is reachable.
|
||||
- Types or client missing/outdated: restart `yarn twenty dev` — it auto-generates the typed client.
|
||||
- Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment.
|
||||
If you run into issues:
|
||||
|
||||
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
- Make sure **Docker is running** before starting the scaffolder with a local instance.
|
||||
- Make sure you are using **Node.js 24+** (`node -v` to check).
|
||||
- Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available.
|
||||
- Try deleting `node_modules` and running `yarn install` again if dependencies seem broken.
|
||||
|
||||
Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322).
|
||||
|
||||
@@ -4,15 +4,75 @@ description: Distribute your Twenty app to the marketplace or deploy it internal
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Apps are currently in alpha testing. The feature is functional but still evolving.
|
||||
Apps are currently in alpha. The feature works but is still evolving.
|
||||
</Warning>
|
||||
|
||||
## Overview
|
||||
|
||||
Once your app is [built and tested locally](/developers/extend/apps/building), you have two paths for distributing it:
|
||||
|
||||
- **Deploy a tarball** — upload your app directly to a specific Twenty server for internal or private use.
|
||||
- **Publish to npm** — list your app in the Twenty marketplace for any workspace to discover and install.
|
||||
- **Push a tarball** — deploy your app to a specific Twenty server for internal use without making it publicly available.
|
||||
|
||||
Both paths start from the same **build** step.
|
||||
|
||||
## Building your app
|
||||
|
||||
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
|
||||
|
||||
## Deploying to a server (tarball)
|
||||
|
||||
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can deploy a tarball directly to a Twenty server.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before deploying, you need a configured remote pointing to the target server. Remotes store the server URL and authentication credentials locally in `~/.twenty/config.json`.
|
||||
|
||||
Add a remote:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Deploying
|
||||
|
||||
Build and upload your app to the server in one step:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Sharing a deployed app
|
||||
|
||||
Tarball apps are not listed in the public marketplace, so other workspaces on the same server won't discover them by browsing. To share a deployed app:
|
||||
|
||||
1. Go to **Settings > Applications > Registrations** and open your app
|
||||
2. In the **Distribution** tab, click **Copy share link**
|
||||
3. Share this link with users on other workspaces — it takes them directly to the app's install page
|
||||
|
||||
The share link uses the server's base URL (without any workspace subdomain) so it works for any workspace on the server.
|
||||
|
||||
<Warning>
|
||||
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
|
||||
</Warning>
|
||||
|
||||
### Version management
|
||||
|
||||
To release an update:
|
||||
|
||||
1. Bump the `version` field in your `package.json`
|
||||
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
|
||||
3. Workspaces that have the app installed will see the upgrade available in their settings
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## Publishing to npm
|
||||
|
||||
@@ -21,29 +81,69 @@ Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twe
|
||||
### Requirements
|
||||
|
||||
- An [npm](https://www.npmjs.com) account
|
||||
- Your package name **must** use the `twenty-app-` prefix (e.g., `twenty-app-postcard-sender`)
|
||||
- The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Build your app** — the CLI compiles your TypeScript sources and generates the application manifest:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Publish to npm** — push the built package to the npm registry:
|
||||
### Marketplace metadata
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Auto-discovery
|
||||
See the [defineApplication accordion](/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
Packages with the `twenty-app-` prefix are automatically discovered by the Twenty marketplace catalog. Once published, your app appears in the marketplace within a few minutes — no manual registration or approval required.
|
||||
### Publish
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
To publish under a specific dist-tag (e.g., `beta` or `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### How marketplace discovery works
|
||||
|
||||
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
|
||||
|
||||
You can trigger the sync immediately instead of waiting:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
|
||||
|
||||
<Note>
|
||||
If your app does not define an `aboutDescription` in `defineApplication()`, the marketplace will automatically use your package's `README.md` from npm as the about page content. This means you can maintain a single README for both npm and the Twenty marketplace. If you want a different description in the marketplace, explicitly set `aboutDescription`.
|
||||
</Note>
|
||||
|
||||
### CI publishing
|
||||
|
||||
The scaffolded project includes a GitHub Actions workflow that publishes on every release. It runs `app:build`, then `npm publish --provenance` from the build output:
|
||||
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `npx twenty build`, then `npm publish` from `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions.
|
||||
</Tip>
|
||||
|
||||
## Internal distribution
|
||||
|
||||
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can push a tarball directly to a Twenty server.
|
||||
|
||||
### Push a tarball
|
||||
|
||||
Build your app and deploy it to a specific server in one step:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
Any workspace on that server can then install and upgrade the app from the **Applications** settings page.
|
||||
|
||||
### Version management
|
||||
|
||||
To release an update:
|
||||
|
||||
1. Bump the `version` field in your `package.json`
|
||||
2. Push a new tarball with `npx twenty publish --server <server-url>`
|
||||
3. Workspaces on that server will see the upgrade available in their settings
|
||||
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty build`, then `npm publish` from `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
Internal apps are scoped to the server they're pushed to. They won't appear in the public marketplace and can't be installed by workspaces on other servers.
|
||||
**npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions.
|
||||
</Note>
|
||||
|
||||
## App categories
|
||||
## Installing apps
|
||||
|
||||
Twenty organizes apps into three categories based on how they're distributed:
|
||||
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
|
||||
|
||||
| Category | How it works | Visible in marketplace? |
|
||||
|----------|-------------|------------------------|
|
||||
| **Development** | Local dev mode apps running via `yarn twenty dev`. Used for building and testing. | No |
|
||||
| **Published** | Apps published to npm with the `twenty-app-` prefix. Listed in the marketplace for any workspace to install. | Yes |
|
||||
| **Internal** | Apps deployed via tarball to a specific server. Available only to workspaces on that server. | No |
|
||||
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
|
||||
|
||||
<Tip>
|
||||
Start in **Development** mode while building your app. When it's ready, choose **Published** (npm) for broad distribution or **Internal** (tarball) for private deployment.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
You can also install apps from the command line:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 788 KiB |
|
After Width: | Height: | Size: 996 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 88 KiB |
@@ -4,84 +4,142 @@ description: أنشئ أول تطبيق Twenty خلال دقائق.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
|
||||
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
|
||||
</Warning>
|
||||
|
||||
تتيح لك التطبيقات توسيع Twenty باستخدام كائنات وحقول ووظائف منطقية ومهارات ذكاء اصطناعي ومكونات واجهة مستخدم مخصصة — جميعها تُدار ككود.
|
||||
|
||||
**ما الذي يمكنك فعله اليوم:**
|
||||
|
||||
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
|
||||
* أنشئ وظائف منطقية مع مشغلات مخصصة (مسارات HTTP، cron، أحداث قاعدة البيانات)
|
||||
* حدد المهارات لوكلاء الذكاء الاصطناعي
|
||||
* أنشئ مكونات واجهية تُعرَض داخل واجهة مستخدم Twenty
|
||||
* انشر التطبيق نفسه عبر مساحات عمل متعددة
|
||||
|
||||
## المتطلبات الأساسية
|
||||
|
||||
* Node.js 24+ وYarn 4
|
||||
* Docker (لخادم تطوير Twenty المحلي)
|
||||
قبل أن تبدأ، تأكّد من تثبيت ما يلي على جهازك:
|
||||
|
||||
## البدء
|
||||
* **Node.js 24+** — [نزّل من هنا](https://nodejs.org/)
|
||||
* **Yarn 4** — يأتي مع Node.js عبر Corepack. قم بتمكينه عبر تشغيل `corepack enable`
|
||||
* **Docker** — [نزّل من هنا](https://www.docker.com/products/docker-desktop/). مطلوب لتشغيل مثيل محلي من Twenty. غير مطلوب إذا كان لديك خادم Twenty قيد التشغيل بالفعل.
|
||||
|
||||
أنشئ تطبيقًا جديدًا باستخدام المُهيئ الرسمي، ثم قم بالمصادقة وابدأ التطوير:
|
||||
## الخطوة 1: إنشاء هيكل تطبيقك
|
||||
|
||||
افتح الطرفية وشغّل:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
سيُطلب منك إدخال اسم ووصف لتطبيقك. اضغط **Enter** لقبول الإعدادات الافتراضية.
|
||||
|
||||
سيؤدي ذلك إلى إنشاء مجلد جديد باسم `my-twenty-app` يحتوي على كل ما تحتاجه.
|
||||
|
||||
<Note>
|
||||
أداة إنشاء الهيكل تدعم الأعلام التالية:
|
||||
|
||||
* `--minimal` — إنشاء الهيكل للملفات الأساسية فقط، بدون أمثلة (افتراضي)
|
||||
* `--exhaustive` — إنشاء الهيكل لجميع كيانات الأمثلة
|
||||
* `--name <name>` — تعيين اسم التطبيق (يتخطى المطالبة)
|
||||
* `--display-name <displayName>` — تعيين اسم العرض (يتخطى المطالبة)
|
||||
* `--description <description>` — تعيين الوصف (يتخطى المطالبة)
|
||||
* `--skip-local-instance` — تخطي مطالبة إعداد الخادم المحلي
|
||||
</Note>
|
||||
|
||||
## الخطوة 2: إعداد مثيل محلي من Twenty
|
||||
|
||||
ستسأل أداة إنشاء الهيكل:
|
||||
|
||||
> **هل ترغب في إعداد مثيل محلي من Twenty؟**
|
||||
|
||||
* **اكتب `yes`** (موصى به) — سيؤدي ذلك إلى سحب صورة Docker `twenty-app-dev` وبدء تشغيل خادم Twenty محلي على المنفذ `2020`. تأكّد من أن Docker قيد التشغيل قبل المتابعة.
|
||||
* **اكتب `no`** — اختر هذا إذا كان لديك خادم Twenty يعمل محليًا بالفعل.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="هل يجب بدء المثيل المحلي؟" />
|
||||
</div>
|
||||
|
||||
## الخطوة 3: سجّل الدخول إلى مساحة العمل الخاصة بك
|
||||
|
||||
بعد ذلك، ستُفتح نافذة متصفح تعرض صفحة تسجيل الدخول الخاصة بـ Twenty. سجّل الدخول باستخدام حساب العرض التوضيحي المُجهَّز مسبقًا:
|
||||
|
||||
* **البريد الإلكتروني:** `tim@apple.dev`
|
||||
* **كلمة المرور:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="شاشة تسجيل الدخول إلى Twenty" />
|
||||
</div>
|
||||
|
||||
## الخطوة 4: تفويض التطبيق
|
||||
|
||||
بعد تسجيل الدخول، ستظهر لك شاشة تفويض. يتيح هذا لتطبيقك التفاعل مع مساحة العمل الخاصة بك.
|
||||
|
||||
انقر **Authorize** للمتابعة.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="شاشة تفويض واجهة الأوامر (CLI) الخاصة بـ Twenty" />
|
||||
</div>
|
||||
|
||||
بمجرد منح التفويض، ستؤكّد الطرفية أن كل شيء قد تم إعداده.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="تم إنشاء هيكل التطبيق بنجاح" />
|
||||
</div>
|
||||
|
||||
## الخطوة 5: ابدأ التطوير
|
||||
|
||||
انتقل إلى مجلد تطبيقك الجديد وابدأ خادم التطوير:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
يدعم المُهيئ وضعين للتحكم في ملفات الأمثلة التي سيتم تضمينها:
|
||||
يقوم هذا بمراقبة ملفات المصدر لديك، وإعادة البناء عند كل تغيير، ومزامنة تطبيقك تلقائيًا مع خادم Twenty المحلي. يفترض أن ترى لوحة حالة مباشرة في الطرفية.
|
||||
|
||||
للحصول على مخرجات أكثر تفصيلاً (سجلات البناء، طلبات المزامنة، تتبعات الأخطاء)، استخدم العلم `--verbose`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
من هنا يمكنك:
|
||||
<Warning>
|
||||
وضع التطوير متاح فقط على مثيلات Twenty التي تعمل في وضع التطوير (`NODE_ENV=development`). المثيلات الإنتاجية ترفض طلبات مزامنة وضع التطوير. استخدم `yarn twenty deploy` للنشر إلى خوادم الإنتاج — اطّلع على [نشر التطبيقات](/l/ar/developers/extend/apps/publishing) للتفاصيل.
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="مخرجات الطرفية في وضع التطوير" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## الخطوة 6: اعرض تطبيقك في Twenty
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
افتح [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) في متصفحك. انتقل إلى **Settings > Apps** واختر علامة التبويب **Developer**. يُفترض أن ترى تطبيقك مُدرجًا تحت **Your Apps**:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="قائمة "Your Apps" تعرض "My twenty app"" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
انقر على **My twenty app** لفتح **تسجيل التطبيق** الخاص به. التسجيل عبارة عن سجل على مستوى الخادم يصف تطبيقك — اسمه، والمعرّف الفريد، وبيانات اعتماد OAuth، والمصدر (محلي، npm، أو tarball). يُخزَّن على الخادم، وليس داخل أي مساحة عمل محددة. عند تثبيت تطبيق في مساحة عمل، ينشئ Twenty **تطبيقًا** بنطاق مساحة العمل يُشير مرة أخرى إلى هذا التسجيل. يمكن تثبيت تسجيل واحد عبر عدة مساحات عمل على الخادم نفسه.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="تفاصيل تسجيل التطبيق" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
انقر **View installed app** لعرض التطبيق المثبّت. تعرض علامة التبويب **About** الإصدار الحالي وخيارات الإدارة:
|
||||
|
||||
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="التطبيق المثبّت — علامة تبويب About" />
|
||||
</div>
|
||||
|
||||
## هيكل المشروع (مُنشأ بالقالب)
|
||||
انتقل إلى علامة التبويب **Content** لمشاهدة كل ما يقدمه تطبيقك — الكائنات، والحقول، ودوال المنطق، والوكلاء:
|
||||
|
||||
عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="التطبيق المثبّت — علامة تبويب Content" />
|
||||
</div>
|
||||
|
||||
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
|
||||
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
|
||||
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
|
||||
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
|
||||
أنت جاهز تمامًا! حرّر أي ملف في `src/` وسيتم التقاط التغييرات تلقائيًا.
|
||||
|
||||
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
|
||||
انتقل إلى [بناء التطبيقات](/l/ar/developers/extend/apps/building) للحصول على دليل مفصّل حول إنشاء الكائنات، ودوال المنطق، ومكونات الواجهة الأمامية، والمهارات، والمزيد.
|
||||
|
||||
---
|
||||
|
||||
## هيكل المشروع
|
||||
|
||||
تولّد أداة إنشاء الهيكل بنية الملفات التالية (مُبيّنة بوضع `--exhaustive` الذي يتضمن أمثلة لكل نوع من الكيانات):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -94,124 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`).
|
||||
افتراضيًا (`--minimal`)، تُنشأ الملفات الأساسية فقط: `application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`. استخدم `--exhaustive` لتضمين جميع ملفات الأمثلة الموضّحة أعلاه.
|
||||
|
||||
بشكل عام:
|
||||
### الملفات الرئيسية
|
||||
|
||||
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة.
|
||||
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
|
||||
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
|
||||
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
|
||||
* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
|
||||
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
|
||||
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
|
||||
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
|
||||
| ملف / مجلد | الغرض |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `package.json` | يصرّح باسم تطبيقك وإصداره واعتماداته. يتضمن نصًا برمجيًا باسم `twenty` بحيث يمكنك تشغيل `yarn twenty help` للاطلاع على جميع الأوامر. |
|
||||
| `src/application-config.ts` | **مطلوب.** ملف الإعداد الرئيسي لتطبيقك. |
|
||||
| `src/roles/` | يعرِّف الأدوار التي تتحكم بما يمكن لدوال المنطق الوصول إليه. |
|
||||
| `src/logic-functions/` | دوال على جانب الخادم يتم تشغيلها عبر المسارات، وجداول cron، أو أحداث قاعدة البيانات. |
|
||||
| `src/front-components/` | مكونات React تُعرَض داخل واجهة مستخدم Twenty. |
|
||||
| `src/objects/` | تعريفات كائنات مخصّصة لتوسيع نموذج البيانات لديك. |
|
||||
| `src/fields/` | حقول مخصّصة تُضاف إلى الكائنات الموجودة. |
|
||||
| `src/views/` | تكوينات العروض المحفوظة. |
|
||||
| `src/navigation-menu-items/` | روابط مخصّصة في شريط التنقل الجانبي. |
|
||||
| `src/skills/` | مهارات توسّع قدرات وكلاء الذكاء الاصطناعي في Twenty. |
|
||||
| `src/agents/` | وكلاء ذكاء اصطناعي مع موجهات مخصّصة. |
|
||||
| `src/page-layouts/` | تخطيطات صفحات مخصّصة لعرض السجلات. |
|
||||
| `src/__tests__/` | اختبارات تكامل (إعداد + اختبار مثال). |
|
||||
| `public/` | أصول ثابتة (صور، خطوط) تُقدَّم مع تطبيقك. |
|
||||
|
||||
### اكتشاف الكيانات
|
||||
## إدارة الريموتات
|
||||
|
||||
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
|
||||
|
||||
| دالة مساعدة | نوع الكيان |
|
||||
| -------------------------------- | ---------------------------------------------- |
|
||||
| `defineObject` | تعريفات كائنات مخصصة |
|
||||
| `defineLogicFunction` | تعريفات الوظائف المنطقية |
|
||||
| `definePreInstallLogicFunction` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) |
|
||||
| `definePostInstallLogicFunction` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) |
|
||||
| `defineFrontComponent` | تعريفات المكونات الواجهية |
|
||||
| `defineRole` | تعريفات الأدوار |
|
||||
| `defineField` | امتدادات الحقول للكائنات الموجودة |
|
||||
| `defineView` | تعريفات العروض المحفوظة |
|
||||
| `defineNavigationMenuItem` | تعريفات عناصر قائمة التنقل |
|
||||
| `defineSkill` | تعريفات مهارات وكلاء الذكاء الاصطناعي |
|
||||
|
||||
<Note>
|
||||
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
|
||||
</Note>
|
||||
|
||||
مثال على كيان تم اكتشافه:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
|
||||
|
||||
* سيقوم `yarn twenty dev` بتوليد عميلين API مضبوطي الأنواع تلقائيًا في `node_modules/twenty-sdk/generated`: `CoreApiClient` (لبيانات مساحة العمل عبر `/graphql`) و`MetadataApiClient` (لتكوين مساحة العمل وتحميل الملفات عبر `/metadata`).
|
||||
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك.
|
||||
|
||||
## المصادقة
|
||||
|
||||
في المرة الأولى التي تشغّل فيها `yarn twenty auth:login`، سيُطلب منك إدخال:
|
||||
|
||||
* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك)
|
||||
* مفتاح واجهة برمجة التطبيقات
|
||||
|
||||
تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. يمكنك الاحتفاظ بملفات تعريف متعددة والتبديل بينها.
|
||||
|
||||
### إدارة مساحات العمل
|
||||
**الريموت** هو خادم Twenty يتصل به تطبيقك. أثناء الإعداد، تُنشئ أداة إنشاء الهيكل واحدًا لك تلقائيًا. يمكنك إضافة ريموتات أخرى أو التبديل بينها في أي وقت.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
بمجرد أن تقوم بالتبديل بين مساحات العمل باستخدام `yarn twenty auth:switch`، ستستخدم جميع الأوامر اللاحقة تلك المساحة افتراضيًا. لا يزال بإمكانك تجاوزه مؤقتًا باستخدام `--workspace <name>`.
|
||||
تُخزَّن بيانات اعتمادك في `~/.twenty/config.json`.
|
||||
|
||||
## خادم التطوير المحلي (`yarn twenty server`)
|
||||
|
||||
يمكن لأداة سطر الأوامر (CLI) إدارة خادم Twenty محلي يعمل داخل Docker. هذا هو الخادم نفسه الذي يبدأ تلقائيًا عند إنشاء هيكل تطبيق باستخدام `create-twenty-app`، لكن يمكنك أيضًا إدارته يدويًا.
|
||||
|
||||
### بدء الخادم
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
سيؤدي ذلك إلى سحب صورة Docker `twentycrm/twenty-app-dev:latest` (إن لم تكن موجودة بالفعل)، وإنشاء حاوية باسم `twenty-app-dev`، وبدء تشغيلها على المنفذ **2020**. تنتظر أداة CLI حتى يجتاز الخادم فحص السلامة قبل الإنهاء.
|
||||
|
||||
يتم إنشاء حجمين في Docker للاحتفاظ بالبيانات بين عمليات إعادة التشغيل:
|
||||
|
||||
* `twenty-app-dev-data` — قاعدة بيانات PostgreSQL
|
||||
* `twenty-app-dev-storage` — تخزين ملفات
|
||||
|
||||
إذا كان المنفذ 2020 مستخدمًا بالفعل، يمكنك البدء على منفذ مختلف:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
تقوم أداة CLI تلقائيًا بتهيئة قيم `NODE_PORT` و`SERVER_URL` الداخلية في الحاوية لتطابق المنفذ المختار، بحيث تعمل دوال المنطق وOAuth وكل الشبكات الداخلية الأخرى بشكل صحيح.
|
||||
|
||||
بمجرد البدء، يُسجَّل الخادم تلقائيًا كـ `local` remote في إعدادات CLI لديك.
|
||||
|
||||
### التحقق من حالة الخادم
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
يعرض ما إذا كان الخادم قيد التشغيل، وعنوان URL الخاص به، وبيانات اعتماد تسجيل الدخول الافتراضية (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### عرض سجلات الخادم
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
يبث سجلات الحاوية. استخدم `--lines` للتحكّم بعدد الأسطر الحديثة المراد عرضها:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### إيقاف الخادم
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
يوقف الحاوية. تُحفَظ بياناتك في أحجام Docker — وستُستأنف الحالة مع عملية `start` التالية من حيث توقفت.
|
||||
|
||||
### إعادة تعيين الخادم
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
يزيل الحاوية **و** يحذف كلا حجمي Docker، ممّا يمحو جميع البيانات. ستنشئ عملية `start` التالية مثيلًا جديدًا من البداية.
|
||||
|
||||
<Note>
|
||||
يتطلّب الخادم أن يكون **Docker** قيد التشغيل. إذا ظهرت لك رسالة خطأ "Docker not running"، فتأكّد من تشغيل Docker Desktop (أو خادوم Docker).
|
||||
</Note>
|
||||
|
||||
### مرجع الأوامر
|
||||
|
||||
| أمر | الوصف |
|
||||
| -------------------------------------- | --------------------------------------------- |
|
||||
| `yarn twenty server start` | بدء الخادم المحلي (يسحب الصورة إذا لزم الأمر) |
|
||||
| `yarn twenty server start --port 3030` | ابدأ على منفذ مخصّص |
|
||||
| `yarn twenty server stop` | إيقاف الخادم (مع الحفاظ على البيانات) |
|
||||
| `yarn twenty server status` | عرض حالة الخادم، وعنوان URL، وبيانات الاعتماد |
|
||||
| `yarn twenty server logs` | بث سجلات الخادم |
|
||||
| `yarn twenty server logs --lines 100` | عرض آخر 100 سطر من السجلات |
|
||||
| `yarn twenty server reset` | حذف جميع البيانات والبدء من جديد |
|
||||
|
||||
## التكامل المستمر (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` في أعلى سير العمل.
|
||||
|
||||
## إعداد يدوي (بدون المهيئ)
|
||||
|
||||
بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي واربط سكربتًا واحدًا في ملف package.json لديك:
|
||||
إذا كنت تفضّل إعداد الأمور بنفسك بدلًا من استخدام `create-twenty-app`، فيمكنك ذلك بخطوتين.
|
||||
|
||||
**1. أضِف `twenty-sdk` و`twenty-client-sdk` كاعتمادات:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
ثم أضف سكربتًا باسم `twenty`:
|
||||
**2. أضِف نصًا برمجيًا باسم `twenty` إلى `package.json` لديك:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -221,25 +393,19 @@ yarn add -D twenty-sdk
|
||||
}
|
||||
```
|
||||
|
||||
الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty <command>`، مثلًا: `yarn twenty dev`، `yarn twenty help`، إلخ.
|
||||
يمكنك الآن تشغيل `yarn twenty dev`، و`yarn twenty help`، وجميع الأوامر الأخرى.
|
||||
|
||||
## كيفية استخدام مثيل محلي من Twenty
|
||||
|
||||
إذا كنت تقوم بتشغيل مثيل محلي من Twenty بالفعل (على سبيل المثال عبر `npx nx start twenty-server`)، فيمكنك الاتصال به بدلًا من استخدام Docker:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
لا تثبّت `twenty-sdk` عالميًا. استخدمه دائمًا كاعتماد محلي للمشروع بحيث يتمكن كل مشروع من تثبيت إصداره الخاص.
|
||||
</Note>
|
||||
|
||||
## استكشاف الأخطاء وإصلاحها
|
||||
|
||||
* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
|
||||
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة برمجة التطبيقات وأن خادم Twenty قابل للوصول.
|
||||
* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty dev` — فهو يولِّد العميل مضبوط الأنواع تلقائيًا.
|
||||
* وضع التطوير لا يزامن: تأكد من أن `yarn twenty dev` قيد التشغيل وأن التغييرات غير متجاهلة في بيئتك.
|
||||
إذا واجهت مشاكل:
|
||||
|
||||
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
* تأكّد من أن **Docker قيد التشغيل** قبل تشغيل أداة إنشاء الهيكل مع مثيل محلي.
|
||||
* تأكّد من أنك تستخدم **Node.js 24+** (`node -v` للتحقق).
|
||||
* تأكّد من **تمكين Corepack** (`corepack enable`) حتى يتوفر Yarn 4.
|
||||
* جرّب حذف `node_modules` وتشغيل `yarn install` مرة أخرى إذا بدت الاعتمادات معطّلة.
|
||||
|
||||
ما زلت عالقًا؟ اطلب المساعدة على [خادم Twenty على Discord](https://discord.com/channels/1130383047699738754/1130386664812982322).
|
||||
|
||||
@@ -4,15 +4,75 @@ description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أ
|
||||
---
|
||||
|
||||
<Warning>
|
||||
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
|
||||
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
|
||||
</Warning>
|
||||
|
||||
## نظرة عامة
|
||||
|
||||
بمجرد أن يكون تطبيقك [مبنيًا ومختبرًا محليًا](/l/ar/developers/extend/apps/building)، لديك مساران لتوزيعه:
|
||||
|
||||
* **نشر أرشيف tar** — ارفع تطبيقك مباشرةً إلى خادم Twenty محدد للاستخدام الداخلي أو الخاص.
|
||||
* **النشر على npm** — أدرج تطبيقك في سوق Twenty ليتسنى لأي مساحة عمل اكتشافه وتثبيته.
|
||||
* **إرسال tarball** — انشر تطبيقك إلى خادم Twenty معيّن للاستخدام الداخلي من دون جعله متاحًا للعامة.
|
||||
|
||||
كلا المسارين يبدآن من نفس خطوة **build**.
|
||||
|
||||
## بناء تطبيقك
|
||||
|
||||
شغّل أمر build لتجميع تطبيقك وإنشاء ملف `manifest.json` جاهز للتوزيع:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
يقوم هذا بتجميع مصادر TypeScript، وتحويل دوال المنطق ومكوّنات الواجهة الأمامية، وكتابة كل شيء إلى `.twenty/output/`. أضِف `--tarball` لإنتاج حزمة `.tgz` أيضًا للتوزيع اليدوي أو لأمر deploy.
|
||||
|
||||
## النشر إلى خادم (tarball)
|
||||
|
||||
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك نشر tarball مباشرةً إلى خادم Twenty.
|
||||
|
||||
### المتطلبات الأساسية
|
||||
|
||||
قبل النشر، تحتاج إلى remote مُعدّ يشير إلى خادم الهدف. تُخزّن remotes عنوان URL للخادم وبيانات اعتماد المصادقة محليًا في `~/.twenty/config.json`.
|
||||
|
||||
أضِف remote:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### النشر
|
||||
|
||||
بناء تطبيقك ورفعه إلى الخادم في خطوة واحدة:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### مشاركة تطبيق منشور
|
||||
|
||||
تطبيقات tarball لا تُدرَج في السوق العامة، لذا لن تكتشفها مساحات العمل الأخرى على الخادم نفسه عبر الاستعراض. لمشاركة تطبيق منشور:
|
||||
|
||||
1. اذهب إلى **الإعدادات > التطبيقات > التسجيلات** وافتح تطبيقك
|
||||
2. في علامة التبويب **التوزيع**، انقر **نسخ رابط المشاركة**
|
||||
3. شارك هذا الرابط مع المستخدمين في مساحات عمل أخرى — سيأخذهم مباشرةً إلى صفحة تثبيت التطبيق
|
||||
|
||||
يستخدم رابط المشاركة عنوان URL الأساسي للخادم (من دون أي نطاق فرعي لمساحة عمل)، لذا يعمل مع أي مساحة عمل على الخادم.
|
||||
|
||||
<Warning>
|
||||
مشاركة التطبيقات الخاصة هي ميزة ضمن باقة Enterprise. اذهب إلى [الإعدادات > لوحة الإدارة > Enterprise](/settings/admin-panel#enterprise) لتمكينها.
|
||||
</Warning>
|
||||
|
||||
### إدارة الإصدارات
|
||||
|
||||
لطرح تحديث:
|
||||
|
||||
1. ارفع قيمة الحقل `version` في ملف `package.json`
|
||||
2. شغّل `yarn twenty deploy` (أو `yarn twenty deploy --remote production`)
|
||||
3. سترى مساحات العمل التي ثبّتت التطبيق الترقية متاحة في إعداداتها
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## النشر على npm
|
||||
|
||||
@@ -21,29 +81,69 @@ description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أ
|
||||
### المتطلبات
|
||||
|
||||
* حساب على [npm](https://www.npmjs.com)
|
||||
* يجب أن يستخدم اسم الحزمة البادئة `twenty-app-` (مثلًا، `twenty-app-postcard-sender`)
|
||||
* الكلمة المفتاحية `twenty-app` في مصفوفة `keywords` في `package.json` (موجودة مسبقًا عند تهيئة المشروع باستخدام `create-twenty-app`)
|
||||
|
||||
### الخطوات
|
||||
|
||||
1. **قم ببناء تطبيقك** — تقوم أداة CLI بتجميع مصادر TypeScript الخاصة بك وإنشاء ملف بيان التطبيق:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **النشر على npm** — ادفع الحزمة المبنية إلى سجل npm:
|
||||
### بيانات التعريف لسوق التطبيقات
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
يدعم إعداد `defineApplication()` حقولًا اختيارية تتحكم في كيفية ظهور تطبيقك في السوق. استخدم `logoUrl` و`screenshots` للإشارة إلى الصور من مجلد `public/`:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### الاكتشاف التلقائي
|
||||
اطّلع على [أكورديون defineApplication](/l/ar/developers/extend/apps/building#defineentity-functions) في صفحة بناء التطبيقات للاطلاع على القائمة الكاملة لحقول السوق (`author` و`category` و`aboutDescription` و`websiteUrl` و`termsUrl` وغيرها).
|
||||
|
||||
تُكتشف الحِزم التي تحمل البادئة `twenty-app-` تلقائيًا بواسطة فهرس سوق Twenty. بعد نشره، سيظهر تطبيقك في السوق خلال بضع دقائق — من دون الحاجة إلى تسجيل يدوي أو موافقة يدوية.
|
||||
### النشر
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
للنشر تحت dist-tag معيّن (مثلًا: `beta` أو `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### كيف تعمل آلية الاكتشاف في السوق
|
||||
|
||||
يقوم خادم Twenty بمزامنة كتالوج السوق من سجل npm **كل ساعة**.
|
||||
|
||||
يمكنك تشغيل المزامنة فورًا بدلًا من الانتظار:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
تأتي بيانات التعريف المعروضة في السوق من إعداد `defineApplication()` — حقول مثل `displayName` و`description` و`author` و`category` و`logoUrl` و`screenshots` و`aboutDescription` و`websiteUrl` و`termsUrl`.
|
||||
|
||||
<Note>
|
||||
إذا لم يحدد تطبيقك `aboutDescription` في `defineApplication()`، فسيستخدم السوق تلقائيًا ملف `README.md` الخاص بحزمتك من npm كمحتوى لصفحة حول. هذا يعني أنه يمكنك الاحتفاظ بملف README واحد لكل من npm وسوق Twenty. إذا كنت تريد وصفًا مختلفًا في السوق، فقم بتعيين `aboutDescription` بشكل صريح.
|
||||
</Note>
|
||||
|
||||
### النشر عبر CI
|
||||
|
||||
يتضمن المشروع المُولَّد سير عمل GitHub Actions يقوم بالنشر عند كل إصدار. يشغِّل `app:build`، ثم ينفِّذ `npm publish --provenance` من مخرجات البناء:
|
||||
استخدم سير عمل GitHub Actions هذا للنشر تلقائيًا مع كل إصدار (يستخدم [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
بالنسبة لأنظمة CI الأخرى (GitLab CI، CircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `npx twenty build`، ثم `npm publish` من `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**npm provenance** اختياري ولكنه موصى به. يضيف النشر باستخدام `--provenance` شارة ثقة إلى إدراجك على npm، مما يتيح للمستخدمين التحقق من أن الحزمة تم بناؤها من التزام محدد ضمن خط أنابيب CI عام. راجع [وثائق npm provenance](https://docs.npmjs.com/generating-provenance-statements) للحصول على تعليمات الإعداد.
|
||||
</Tip>
|
||||
|
||||
## التوزيع الداخلي
|
||||
|
||||
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك إرسال tarball مباشرةً إلى خادم Twenty.
|
||||
|
||||
### إرسال tarball
|
||||
|
||||
قم ببناء تطبيقك وانشره إلى خادم محدد في خطوة واحدة:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
يمكن لأي مساحة عمل على ذلك الخادم بعدها تثبيت التطبيق وترقيته من صفحة الإعدادات **التطبيقات**.
|
||||
|
||||
### إدارة الإصدارات
|
||||
|
||||
لطرح تحديث:
|
||||
|
||||
1. ارفع قيمة الحقل `version` في ملف `package.json`
|
||||
2. أرسل tarball جديدًا باستخدام `npx twenty publish --server <server-url>`
|
||||
3. سترى مساحات العمل على ذلك الخادم الترقية متاحة في إعداداتها
|
||||
بالنسبة لأنظمة CI الأخرى (GitLab CI، وCircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `yarn twenty build`، ثم `npm publish` من `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
التطبيقات الداخلية مقتصرة على الخادم الذي تُرسل إليه. لن تظهر في السوق العام ولا يمكن لمساحات العمل على خوادم أخرى تثبيتها.
|
||||
**npm provenance** اختياري ولكنه موصى به. يضيف النشر باستخدام `--provenance` شارة ثقة إلى إدراجك على npm، مما يتيح للمستخدمين التحقق من أن الحزمة تم بناؤها من التزام محدد ضمن خط أنابيب CI عام. راجع [وثائق npm provenance](https://docs.npmjs.com/generating-provenance-statements) للحصول على تعليمات الإعداد.
|
||||
</Note>
|
||||
|
||||
## فئات التطبيقات
|
||||
## تثبيت التطبيقات
|
||||
|
||||
تُنظِّم Twenty التطبيقات في ثلاث فئات استنادًا إلى طريقة توزيعها:
|
||||
بعد نشر التطبيق (npm) أو نشره (tarball)، يمكن لمساحات العمل تثبيته عبر واجهة المستخدم.
|
||||
|
||||
| الفئة | كيف يعمل | مرئي في سوق Twenty؟ |
|
||||
| ----------- | ---------------------------------------------------------------------------------------------------- | ------------------- |
|
||||
| **التطوير** | تطبيقات وضع التطوير المحلي التي تعمل عبر `yarn twenty dev`. تُستخدم للبناء والاختبار. | لا |
|
||||
| **منشور** | تطبيقات منشورة على npm مع البادئة `twenty-app-`. مدرجة في سوق Twenty لتتمكن أي مساحة عمل من تثبيتها. | نعم |
|
||||
| **داخلي** | تطبيقات منشورة عبر tarball إلى خادم محدد. متاحة فقط لمساحات العمل على ذلك الخادم. | لا |
|
||||
اذهب إلى صفحة **الإعدادات > التطبيقات** في Twenty، حيث يمكن استعراض تطبيقات السوق والتطبيقات المنشورة عبر tarball وتثبيتها.
|
||||
|
||||
<Tip>
|
||||
ابدأ في وضع **التطوير** أثناء بناء تطبيقك. عندما يصبح جاهزًا، اختر **منشور** (npm) للتوزيع الواسع أو **داخلي** (tarball) للنشر الخاص.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
يمكنك أيضًا تثبيت التطبيقات من سطر الأوامر:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
@@ -55,11 +55,12 @@ export const main = async (
|
||||
params: { companyId: string },
|
||||
) => {
|
||||
const { companyId } = params;
|
||||
|
||||
// Replace with your Twenty GraphQL endpoint
|
||||
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
|
||||
// Cloud: https://api.twenty.com/graphql
|
||||
// Self-hosted: https://your-domain.com/graphql
|
||||
const graphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
|
||||
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
// Replace with your API key from Settings → APIs
|
||||
const authToken = 'YOUR_API_KEY';
|
||||
@@ -79,11 +80,40 @@ export const main = async (
|
||||
const pdfBlob = await pdfResponse.blob();
|
||||
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
|
||||
|
||||
// Step 2: Upload the file via GraphQL multipart upload
|
||||
const fieldMetadataIdQuery = `
|
||||
query FindUploadFileFieldMetadataId {
|
||||
objects {
|
||||
edges {
|
||||
node {
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
|
||||
const response = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
query: fieldMetadataIdQuery,
|
||||
}
|
||||
});
|
||||
const result = await response.json();
|
||||
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
|
||||
|
||||
// Step 3: Upload the file via GraphQL multipart upload
|
||||
const uploadMutation = `
|
||||
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
|
||||
uploadFile(file: $file, fileFolder: $fileFolder) {
|
||||
path
|
||||
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
|
||||
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -91,12 +121,12 @@ export const main = async (
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.append('operations', JSON.stringify({
|
||||
query: uploadMutation,
|
||||
variables: { file: null, fileFolder: 'Attachment' },
|
||||
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
|
||||
}));
|
||||
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
|
||||
uploadForm.append('0', pdfFile);
|
||||
|
||||
const uploadResponse = await fetch(graphqlEndpoint, {
|
||||
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
body: uploadForm,
|
||||
@@ -108,15 +138,15 @@ export const main = async (
|
||||
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
|
||||
}
|
||||
|
||||
const filePath = uploadResult.data?.uploadFile?.path;
|
||||
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('No file path returned from upload');
|
||||
if (!fileId) {
|
||||
throw new Error('No file id returned from upload');
|
||||
}
|
||||
|
||||
// Step 3: Create the attachment linked to the company
|
||||
// Step 4: Create the attachment linked to the company
|
||||
const attachmentMutation = `
|
||||
mutation CreateAttachment($data: AttachmentCreateInput!) {
|
||||
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
|
||||
createAttachment(data: $data) {
|
||||
id
|
||||
name
|
||||
@@ -124,7 +154,7 @@ export const main = async (
|
||||
}
|
||||
`;
|
||||
|
||||
const attachmentResponse = await fetch(graphqlEndpoint, {
|
||||
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -135,8 +165,13 @@ export const main = async (
|
||||
variables: {
|
||||
data: {
|
||||
name: filename,
|
||||
fullPath: filePath,
|
||||
companyId,
|
||||
targetCompanyId: companyId,
|
||||
file: [
|
||||
{
|
||||
fileId: fileId,
|
||||
label: filename
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -156,14 +191,14 @@ export const main = async (
|
||||
|
||||
#### لإرفاقه بكائن مختلف
|
||||
|
||||
استبدل `companyId` بالحقل المناسب:
|
||||
استبدل `targetCompanyId` بالحقل المناسب:
|
||||
|
||||
| كائن | اسم الحقل |
|
||||
| ---------- | -------------------- |
|
||||
| الشركة | `companyId` |
|
||||
| شخص | `personId` |
|
||||
| الفرصة | `opportunityId` |
|
||||
| كائن مخصّص | `yourCustomObjectId` |
|
||||
| كائن | اسم الحقل |
|
||||
| ---------- | -------------------------- |
|
||||
| الشركة | `targetCompanyId` |
|
||||
| شخص | `targetPersonId` |
|
||||
| الفرصة | `targetOpportunityId` |
|
||||
| كائن مخصّص | `targetYourCustomObjectId` |
|
||||
|
||||
حدّث كل من معلمة الوظيفة وكائن `variables.data` في عملية الـ mutation الخاصة بالمرفق.
|
||||
|
||||
|
||||
@@ -4,84 +4,142 @@ description: Vytvořte svou první aplikaci Twenty během několika minut.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí.
|
||||
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
Aplikace vám umožňují rozšířit Twenty o vlastní objekty, pole, logické funkce, AI schopnosti a komponenty uživatelského rozhraní — vše je spravováno jako kód.
|
||||
|
||||
**Co můžete dělat už dnes:**
|
||||
|
||||
* Definujte vlastní objekty a pole jako kód (spravovaný datový model)
|
||||
* Vytvářejte logické funkce s vlastními spouštěči (HTTP routy, cron, databázové události)
|
||||
* Definujte dovednosti agentů AI
|
||||
* Vytvářejte frontendové komponenty, které se vykreslují uvnitř uživatelského rozhraní Twenty
|
||||
* Nasazujte stejnou aplikaci do více pracovních prostorů
|
||||
|
||||
## Předpoklady
|
||||
|
||||
* Node.js 24+ a Yarn 4
|
||||
* Docker (pro místní vývojový server Twenty)
|
||||
Než začnete, ujistěte se, že máte ve svém počítači nainstalováno následující:
|
||||
|
||||
## Začínáme
|
||||
* **Node.js 24+** — [Stáhnout zde](https://nodejs.org/)
|
||||
* **Yarn 4** — Dodává se s Node.js prostřednictvím Corepacku. Povolte jej spuštěním `corepack enable`
|
||||
* **Docker** — [Stáhnout zde](https://www.docker.com/products/docker-desktop/). Nutné pro spuštění lokální instance Twenty. Není potřeba, pokud už máte spuštěný server Twenty.
|
||||
|
||||
Vytvořte novou aplikaci pomocí oficiálního scaffolderu, poté se ověřte a začněte vyvíjet:
|
||||
## Krok 1: Vytvořte kostru své aplikace
|
||||
|
||||
Otevřete terminál a spusťte:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
Budete vyzváni k zadání názvu a popisu své aplikace. Stisknutím **Enter** přijmete výchozí hodnoty.
|
||||
|
||||
Tím se vytvoří nová složka s názvem `my-twenty-app` se vším potřebným.
|
||||
|
||||
<Note>
|
||||
Generátor kostry podporuje tyto přepínače:
|
||||
|
||||
* `--minimal` — vygeneruje pouze nezbytné soubory, bez příkladů (výchozí)
|
||||
* `--exhaustive` — vygeneruje všechny ukázkové entity
|
||||
* `--name <name>` — nastaví název aplikace (přeskočí výzvu)
|
||||
* `--display-name <displayName>` — nastaví zobrazovaný název (přeskočí výzvu)
|
||||
* `--description <description>` — nastaví popis (přeskočí výzvu)
|
||||
* `--skip-local-instance` — přeskočí výzvu k nastavení lokálního serveru
|
||||
</Note>
|
||||
|
||||
## Krok 2: Nastavte lokální instanci Twenty
|
||||
|
||||
Generátor kostry se zeptá:
|
||||
|
||||
> **Chcete nastavit lokální instanci Twenty?**
|
||||
|
||||
* **Zadejte `yes`** (doporučeno) — Stáhne image Dockeru `twenty-app-dev` a spustí lokální server Twenty na portu `2020`. Než budete pokračovat, ujistěte se, že Docker běží.
|
||||
* **Zadejte `no`** — Zvolte, pokud už máte lokálně spuštěný server Twenty.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Spustit lokální instanci?" />
|
||||
</div>
|
||||
|
||||
## Krok 3: Přihlaste se do svého pracovního prostoru
|
||||
|
||||
Poté se otevře okno prohlížeče se stránkou přihlášení do Twenty. Přihlaste se předpřipraveným demo účtem:
|
||||
|
||||
* **E-mail:** `tim@apple.dev`
|
||||
* **Heslo:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Přihlašovací obrazovka Twenty" />
|
||||
</div>
|
||||
|
||||
## Krok 4: Autorizujte aplikaci
|
||||
|
||||
Po přihlášení uvidíte autorizační obrazovku. Tím umožníte vaší aplikaci pracovat s vaším pracovním prostorem.
|
||||
|
||||
Pokračujte kliknutím na **Authorize**.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Autorizační obrazovka Twenty CLI" />
|
||||
</div>
|
||||
|
||||
Po autorizaci váš terminál potvrdí, že je vše nastaveno.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Aplikace byla úspěšně vygenerována" />
|
||||
</div>
|
||||
|
||||
## Krok 5: Začněte vyvíjet
|
||||
|
||||
Přejděte do nové složky aplikace a spusťte vývojový server:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
Nástroj pro generování kostry podporuje dva režimy pro řízení toho, které ukázkové soubory jsou zahrnuty:
|
||||
Sleduje zdrojové soubory, při každé změně znovu sestaví a automaticky synchronizuje vaši aplikaci s lokálním serverem Twenty. V terminálu byste měli vidět panel se stavem v reálném čase.
|
||||
|
||||
Pro podrobnější výstup (protokoly sestavení, požadavky na synchronizaci, stopy chyb) použijte přepínač `--verbose`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
Odtud můžete:
|
||||
<Warning>
|
||||
Vývojový režim je k dispozici pouze na instancích Twenty běžících v režimu development (`NODE_ENV=development`). Produkční instance odmítají požadavky na vývojovou synchronizaci. Pro nasazení na produkční servery použijte `yarn twenty deploy` — podrobnosti viz [Publikování aplikací](/l/cs/developers/extend/apps/publishing).
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Výstup terminálu ve vývojovém režimu" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## Krok 6: Zobrazte svou aplikaci v Twenty
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
Otevřete ve svém prohlížeči [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer). Přejděte do **Settings > Apps** a vyberte kartu **Developer**. Vaše aplikace by měla být uvedena v části **Your Apps**:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Seznam Your Apps zobrazující My twenty app" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
Klikněte na **My twenty app** a otevřete její **registraci aplikace**. Registrace je záznam na úrovni serveru, který popisuje vaši aplikaci — její název, jedinečný identifikátor, přihlašovací údaje OAuth a zdroj (lokální, npm nebo tarball). Existuje na serveru, ne uvnitř žádného konkrétního pracovního prostoru. Když nainstalujete aplikaci do pracovního prostoru, Twenty vytvoří **aplikaci** v rozsahu pracovního prostoru, která odkazuje zpět na tuto registraci. Jedna registrace může být nainstalována ve více pracovních prostorech na stejném serveru.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Podrobnosti registrace aplikace" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
Klikněte na **View installed app**, abyste zobrazili nainstalovanou aplikaci. Karta **About** zobrazuje aktuální verzi a možnosti správy:
|
||||
|
||||
Viz také: referenční stránky CLI pro [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) a [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Nainstalovaná aplikace — karta About" />
|
||||
</div>
|
||||
|
||||
## Struktura projektu (vytvořená scaffolderem)
|
||||
Přepněte na kartu **Content**, abyste viděli vše, co vaše aplikace poskytuje — objekty, pole, logické funkce a agenty:
|
||||
|
||||
Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Nainstalovaná aplikace — karta Content" />
|
||||
</div>
|
||||
|
||||
* Zkopíruje minimální základní aplikaci do `my-twenty-app/`
|
||||
* Přidá lokální závislost `twenty-sdk` a konfiguraci pro Yarn 4
|
||||
* Vytvoří konfigurační soubory a skripty napojené na `twenty` CLI
|
||||
* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, předinstalační a postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry
|
||||
Vše je připraveno! Upravte libovolný soubor v `src/` a změny se automaticky projeví.
|
||||
|
||||
Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto:
|
||||
Přejděte na [Tvorba aplikací](/l/cs/developers/extend/apps/building) pro podrobný průvodce vytvářením objektů, logických funkcí, frontendových komponent, dovedností a dalšího.
|
||||
|
||||
---
|
||||
|
||||
## Struktura projektu
|
||||
|
||||
Generátor kostry vytvoří následující strukturu souborů (zobrazeno v režimu `--exhaustive`, který zahrnuje příklady pro každý typ entity):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -94,124 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`).
|
||||
Ve výchozím nastavení (`--minimal`) se vytvoří pouze základní soubory: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`. Pro zahrnutí všech ukázkových souborů výše použijte `--exhaustive`.
|
||||
|
||||
V kostce:
|
||||
### Klíčové soubory
|
||||
|
||||
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skript `twenty`, který deleguje na lokální `twenty` CLI. Spusťte `yarn twenty help` pro výpis všech dostupných příkazů.
|
||||
* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `generated/` (typovaný klient), `dist/`, `build/`, složky s coverage, logy a soubory `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem.
|
||||
* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem.
|
||||
* **.oxlintrc.json** a **tsconfig.json**: Poskytují lintování a konfiguraci TypeScriptu pro zdrojové soubory vaší aplikace v TypeScriptu.
|
||||
* **README.md**: Krátké README v kořeni aplikace se základními pokyny.
|
||||
* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné.
|
||||
* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód
|
||||
| Soubor / Složka | Účel |
|
||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `package.json` | Definuje název, verzi a závislosti vaší aplikace. Obsahuje skript `twenty`, takže můžete spustit `yarn twenty help` a zobrazit všechny příkazy. |
|
||||
| `src/application-config.ts` | **Povinné.** Hlavní konfigurační soubor vaší aplikace. |
|
||||
| `src/roles/` | Definuje role, které určují, k čemu mají vaše logické funkce přístup. |
|
||||
| `src/logic-functions/` | Serverové funkce spouštěné trasami, plánovačem cron nebo událostmi databáze. |
|
||||
| `src/front-components/` | Komponenty Reactu, které se vykreslují uvnitř uživatelského rozhraní Twenty. |
|
||||
| `src/objects/` | Vlastní definice objektů pro rozšíření vašeho datového modelu. |
|
||||
| `src/fields/` | Vlastní pole přidaná k existujícím objektům. |
|
||||
| `src/views/` | Konfigurace uložených zobrazení. |
|
||||
| `src/navigation-menu-items/` | Vlastní odkazy v postranní navigaci. |
|
||||
| `src/skills/` | Dovednosti, které rozšiřují možnosti AI agentů Twenty. |
|
||||
| `src/agents/` | AI agenti s vlastními prompty. |
|
||||
| `src/page-layouts/` | Vlastní rozvržení stránek pro zobrazení záznamů. |
|
||||
| `src/__tests__/` | Integrační testy (nastavení + ukázkový test). |
|
||||
| `public/` | Statická aktiva (obrázky, písma) poskytovaná s vaší aplikací. |
|
||||
|
||||
### Detekce entit
|
||||
## Správa vzdálených serverů
|
||||
|
||||
SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define<Entity>({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`:
|
||||
|
||||
| Pomocná funkce | Typ entity |
|
||||
| -------------------------------- | --------------------------------------------------------- |
|
||||
| `defineObject` | Definice vlastních objektů |
|
||||
| `defineLogicFunction` | Definice logických funkcí |
|
||||
| `definePreInstallLogicFunction` | Předinstalační logická funkce (spouští se před instalací) |
|
||||
| `definePostInstallLogicFunction` | Postinstalační logická funkce (spouští se po instalaci) |
|
||||
| `defineFrontComponent` | Definice frontendových komponent |
|
||||
| `defineRole` | Definice rolí |
|
||||
| `defineField` | Rozšíření polí u existujících objektů |
|
||||
| `defineView` | Definice uložených zobrazení |
|
||||
| `defineNavigationMenuItem` | Definice položek navigační nabídky |
|
||||
| `defineSkill` | Definice dovedností agenta AI |
|
||||
|
||||
<Note>
|
||||
**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define<Entity>({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek.
|
||||
</Note>
|
||||
|
||||
Příklad detekované entity:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Pozdější příkazy přidají další soubory a složky:
|
||||
|
||||
* `yarn twenty dev` automaticky vygeneruje dva typované API klienty v `node_modules/twenty-sdk/generated`: `CoreApiClient` (pro data pracovního prostoru přes `/graphql`) a `MetadataApiClient` (pro konfiguraci pracovního prostoru a nahrávání souborů přes `/metadata`).
|
||||
* `yarn twenty entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty, role, dovednosti a další.
|
||||
|
||||
## Ověření
|
||||
|
||||
Při prvním spuštění `yarn twenty auth:login` budete vyzváni k zadání:
|
||||
|
||||
* URL API (výchozí je http://localhost:3000 nebo váš aktuální profil pracovního prostoru)
|
||||
* Klíč API
|
||||
|
||||
Vaše přihlašovací údaje se ukládají pro jednotlivé uživatele do `~/.twenty/config.json`. Můžete spravovat více profilů a přepínat mezi nimi.
|
||||
|
||||
### Správa pracovních prostorů
|
||||
**Remote** je server Twenty, ke kterému se vaše aplikace připojuje. Během nastavení jej generátor kostry automaticky vytvoří. Můžete kdykoli přidat další vzdálené servery nebo mezi nimi přepínat.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Jakmile přepnete pracovní prostor pomocí `yarn twenty auth:switch`, všechny následující příkazy budou tento pracovní prostor používat jako výchozí. Můžete jej stále dočasně přepsat pomocí `--workspace <name>`.
|
||||
Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`.
|
||||
|
||||
## Lokální vývojový server (`yarn twenty server`)
|
||||
|
||||
CLI může spravovat lokální server Twenty běžící v Dockeru. Jde o stejný server, který se spustí automaticky při vytvoření kostry aplikace pomocí `create-twenty-app`, ale můžete jej spravovat i ručně.
|
||||
|
||||
### Spuštění serveru
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
Stáhne image Dockeru `twentycrm/twenty-app-dev:latest` (pokud již není k dispozici), vytvoří kontejner s názvem `twenty-app-dev` a spustí jej na portu **2020**. CLI čeká, dokud server neprojde kontrolou stavu, než vrátí řízení.
|
||||
|
||||
Vytvoří se dva svazky Dockeru pro zachování dat mezi restartováními:
|
||||
|
||||
* `twenty-app-dev-data` — databáze PostgreSQL
|
||||
* `twenty-app-dev-storage` — úložiště souborů
|
||||
|
||||
Pokud je port 2020 již používán, můžete spustit na jiném portu:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
CLI automaticky nakonfiguruje interní `NODE_PORT` a `SERVER_URL` kontejneru tak, aby odpovídaly zvolenému portu, takže logické funkce, OAuth a veškerá ostatní vnitřní síťová komunikace fungují správně.
|
||||
|
||||
Po spuštění je server automaticky zaregistrován jako `local` remote ve vaší konfiguraci CLI.
|
||||
|
||||
### Kontrola stavu serveru
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
Zobrazí, zda server běží, jeho URL a výchozí přihlašovací údaje (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### Zobrazení protokolů serveru
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
Streamuje protokoly kontejneru. Pomocí `--lines` ovládnete, kolik posledních řádků se má zobrazit:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### Zastavení serveru
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
Zastaví kontejner. Vaše data jsou zachována ve svazcích Dockeru — další `start` naváže tam, kde jste skončili.
|
||||
|
||||
### Resetování serveru
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
Odstraní kontejner **a** smaže oba svazky Dockeru, čímž vymaže všechna data. Další `start` vytvoří čistou instanci.
|
||||
|
||||
<Note>
|
||||
Server vyžaduje, aby **Docker** běžel. Pokud vidíte chybu "Docker not running", ujistěte se, že je spuštěný Docker Desktop (nebo démon Dockeru).
|
||||
</Note>
|
||||
|
||||
### Přehled příkazů
|
||||
|
||||
| Příkaz | Popis |
|
||||
| -------------------------------------- | ------------------------------------------------------ |
|
||||
| `yarn twenty server start` | Spustí lokální server (v případě potřeby stáhne image) |
|
||||
| `yarn twenty server start --port 3030` | Spustí na vlastním portu |
|
||||
| `yarn twenty server stop` | Zastaví server (zachová data) |
|
||||
| `yarn twenty server status` | Zobrazí stav serveru, URL a přihlašovací údaje |
|
||||
| `yarn twenty server logs` | Streamuje protokoly serveru |
|
||||
| `yarn twenty server logs --lines 100` | Zobrazí posledních 100 řádků logu |
|
||||
| `yarn twenty server reset` | Smaže všechna data a začne znovu |
|
||||
|
||||
## 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í efemérní 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.
|
||||
|
||||
## Ruční nastavení (bez scaffolderu)
|
||||
|
||||
Ačkoli pro nejlepší začátky doporučujeme použít `create-twenty-app`, projekt můžete nastavit i ručně. Neinstalujte CLI globálně. Místo toho přidejte `twenty-sdk` jako lokální závislost a přidejte jeden skript do souboru package.json:
|
||||
Pokud dáváte přednost vlastnímu nastavení místo použití `create-twenty-app`, můžete to udělat ve dvou krocích.
|
||||
|
||||
**1. Přidejte `twenty-sdk` a `twenty-client-sdk` jako závislosti:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Poté přidejte skript `twenty`:
|
||||
**2. Přidejte skript `twenty` do svého `package.json`:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -221,25 +393,19 @@ Poté přidejte skript `twenty`:
|
||||
}
|
||||
```
|
||||
|
||||
Nyní můžete spouštět všechny příkazy přes `yarn twenty <command>`, např. `yarn twenty dev`, `yarn twenty help` atd.
|
||||
Nyní můžete spouštět `yarn twenty dev`, `yarn twenty help` a všechny ostatní příkazy.
|
||||
|
||||
## Jak používat lokální instanci Twenty
|
||||
|
||||
Pokud již lokálně provozujete instanci Twenty (např. pomocí `npx nx start twenty-server`), můžete se k ní připojit namísto použití Dockeru:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
Neinstalujte `twenty-sdk` globálně. Vždy jej používejte jako lokální závislost projektu, aby si každý projekt mohl připnout svou vlastní verzi.
|
||||
</Note>
|
||||
|
||||
## Řešení potíží
|
||||
|
||||
* Chyby ověření: spusťte `yarn twenty auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění.
|
||||
* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný.
|
||||
* Typy nebo klient chybí nebo jsou zastaralé: restartujte `yarn twenty dev` — automaticky generuje typovaného klienta.
|
||||
* Režim vývoje se nesynchronizuje: ujistěte se, že běží `yarn twenty dev` a že vaše prostředí změny neignoruje.
|
||||
Pokud narazíte na potíže:
|
||||
|
||||
Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
* Před spuštěním generátoru kostry s lokální instancí se ujistěte, že **Docker běží**.
|
||||
* Ujistěte se, že používáte **Node.js 24+** (ověříte příkazem `node -v`).
|
||||
* Ujistěte se, že je **Corepack povolen** (`corepack enable`), aby byl k dispozici Yarn 4.
|
||||
* Zkuste smazat `node_modules` a znovu spustit `yarn install`, pokud se zdají závislosti poškozené.
|
||||
|
||||
Pořád se nedaří? Požádejte o pomoc na [Discordu Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
|
||||
|
||||
@@ -4,15 +4,75 @@ description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte i
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí.
|
||||
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
## Přehled
|
||||
|
||||
Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/building), máte dvě cesty, jak ji distribuovat:
|
||||
|
||||
* **Nasaďte tarball** — nahrajte svou aplikaci přímo na konkrétní server Twenty pro interní nebo soukromé použití.
|
||||
* **Publish to npm** — uveďte svou aplikaci v Marketplace Twenty, aby ji mohl kterýkoli pracovní prostor objevit a nainstalovat.
|
||||
* **Odeslat tarball** — nasaďte svou aplikaci na konkrétní server Twenty pro interní použití, aniž by byla veřejně dostupná.
|
||||
|
||||
Obě cesty začínají stejným krokem **build**.
|
||||
|
||||
## Sestavení vaší aplikace
|
||||
|
||||
Spusťte příkaz build ke zkompilování své aplikace a k vygenerování souboru `manifest.json` připraveného k distribuci:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
Tím se zkompilují zdrojové soubory TypeScriptu, transpilují logické funkce a frontendové komponenty a vše se zapíše do `.twenty/output/`. Přidejte `--tarball`, abyste také vytvořili balíček `.tgz` pro ruční distribuci nebo příkaz deploy.
|
||||
|
||||
## Nasazení na server (tarball)
|
||||
|
||||
U aplikací, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete nasadit tarball přímo na server Twenty.
|
||||
|
||||
### Předpoklady
|
||||
|
||||
Před nasazením potřebujete nakonfigurovaný vzdálený cíl směřující na cílový server. Vzdálené cíle ukládají adresu URL serveru a přihlašovací údaje lokálně v `~/.twenty/config.json`.
|
||||
|
||||
Přidat vzdálený cíl:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Nasazení
|
||||
|
||||
Sestavte a nahrajte svou aplikaci na server v jednom kroku:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Sdílení nasazené aplikace
|
||||
|
||||
Aplikace ve formě tarball nejsou uvedeny ve veřejném tržišti, takže je ostatní pracovní prostory na tomtéž serveru procházením neobjeví. Chcete-li sdílet nasazenou aplikaci:
|
||||
|
||||
1. Přejděte do **Nastavení > Aplikace > Registrace** a otevřete svou aplikaci
|
||||
2. Na kartě **Distribuce** klikněte na **Zkopírovat odkaz ke sdílení**
|
||||
3. Sdílejte tento odkaz s uživateli v jiných pracovních prostorech — zavede je přímo na instalační stránku aplikace
|
||||
|
||||
Odkaz ke sdílení používá základní adresu URL serveru (bez jakékoli subdomény pracovního prostoru), takže funguje pro libovolný pracovní prostor na serveru.
|
||||
|
||||
<Warning>
|
||||
Sdílení soukromých aplikací je funkce Enterprise. Přejděte do [Nastavení > Admin Panel > Enterprise](/settings/admin-panel#enterprise) a povolte ji.
|
||||
</Warning>
|
||||
|
||||
### Správa verzí
|
||||
|
||||
Chcete-li vydat aktualizaci:
|
||||
|
||||
1. Zvyšte hodnotu pole `version` v souboru `package.json`
|
||||
2. Spusťte `yarn twenty deploy` (nebo `yarn twenty deploy --remote production`)
|
||||
3. Pracovní prostory, které mají aplikaci nainstalovanou, uvidí dostupnou aktualizaci ve svém nastavení
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## Publikování na npm
|
||||
|
||||
@@ -21,29 +81,69 @@ Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketpla
|
||||
### Požadavky
|
||||
|
||||
* Účet na [npm](https://www.npmjs.com)
|
||||
* Název vašeho balíčku **musí** používat předponu `twenty-app-` (např. `twenty-app-postcard-sender`)
|
||||
* Klíčové slovo `twenty-app` ve vašem poli `keywords` v souboru `package.json` (již je zahrnuto, když založíte projekt pomocí `create-twenty-app`)
|
||||
|
||||
### Postup
|
||||
|
||||
1. **Sestavte svou aplikaci** — CLI zkompiluje vaše zdrojové soubory TypeScript a vygeneruje manifest aplikace:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Publikujte na npm** — odešlete sestavený balíček do registru npm:
|
||||
### Metadata tržiště
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
Konfigurace `defineApplication()` podporuje volitelná pole, která určují, jak se vaše aplikace zobrazuje v tržišti. Použijte `logoUrl` a `screenshots` k odkazování na obrázky ze složky `public/`:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Automatické rozpoznání
|
||||
Podívejte se na [sekci defineApplication](/l/cs/developers/extend/apps/building#defineentity-functions) na stránce Building Apps pro úplný seznam polí tržiště (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` atd.).
|
||||
|
||||
Balíčky s předponou `twenty-app-` jsou automaticky rozpoznávány katalogem Marketplace Twenty. Po publikování se vaše aplikace během několika minut objeví v Marketplace — nevyžaduje žádnou ruční registraci ani schvalování.
|
||||
### Publikování
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
Chcete-li publikovat pod konkrétním dist-tagem (např. `beta` nebo `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### Jak funguje objevování v tržišti
|
||||
|
||||
Server Twenty synchronizuje svůj katalog tržiště z registru npm **každou hodinu**.
|
||||
|
||||
Synchronizaci můžete spustit okamžitě místo čekání:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
Metadata zobrazená v tržišti pocházejí z vaší konfigurace `defineApplication()` — z polí jako `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` a `termsUrl`.
|
||||
|
||||
<Note>
|
||||
Pokud vaše aplikace nedefinuje `aboutDescription` v `defineApplication()`, tržiště automaticky použije soubor `README.md` vašeho balíčku z npm jako obsah stránky O aplikaci. To znamená, že můžete spravovat jediný soubor README jak pro npm, tak pro tržiště Twenty. Pokud chcete v tržišti jiný popis, explicitně nastavte `aboutDescription`.
|
||||
</Note>
|
||||
|
||||
### Publikování pomocí CI
|
||||
|
||||
Vygenerovaný projekt obsahuje pracovní postup GitHub Actions, který publikuje při každém vydání. Spouští `app:build`, a poté `npm publish --provenance` z výstupu buildu:
|
||||
Použijte tento pracovní postup GitHub Actions k automatickému publikování při každém vydání (používá [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
Pro jiné systémy CI (GitLab CI, CircleCI atd.) platí stejné tři příkazy: `yarn install`, `npx twenty build` a poté `npm publish` z `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**npm provenance** je volitelné, ale doporučené. Publikování s `--provenance` přidá k vašemu záznamu na npm odznak důvěryhodnosti a umožní uživatelům ověřit, že balíček byl sestaven z konkrétního commitu ve veřejné CI pipeline. Pokyny k nastavení najdete v [dokumentaci k npm provenance](https://docs.npmjs.com/generating-provenance-statements).
|
||||
</Tip>
|
||||
|
||||
## Interní distribuce
|
||||
|
||||
Pro aplikace, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete odeslat tarball přímo na server Twenty.
|
||||
|
||||
### Odeslat tarball
|
||||
|
||||
Sestavte svou aplikaci a nasaďte ji na konkrétní server v jednom kroku:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
Jakýkoli pracovní prostor na tomto serveru pak může aplikaci instalovat a aktualizovat ze stránky nastavení **Applications**.
|
||||
|
||||
### Správa verzí
|
||||
|
||||
Chcete-li vydat aktualizaci:
|
||||
|
||||
1. Zvyšte hodnotu pole `version` v souboru `package.json`
|
||||
2. Odešlete nový tarball pomocí `npx twenty publish --server <server-url>`
|
||||
3. Pracovní prostory na tomto serveru uvidí dostupnou aktualizaci ve svém nastavení
|
||||
Pro jiné systémy CI (GitLab CI, CircleCI atd.) platí stejné tři příkazy: `yarn install`, `yarn twenty build` a poté `npm publish` z `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
Interní aplikace jsou omezené na server, na který jsou odeslány. Nezobrazí se ve veřejném Marketplace a nelze je instalovat v pracovních prostorech na jiných serverech.
|
||||
**npm provenance** je volitelné, ale doporučené. Publikování s `--provenance` přidá k vašemu záznamu na npm odznak důvěryhodnosti a umožní uživatelům ověřit, že balíček byl sestaven z konkrétního commitu ve veřejné CI pipeline. Pokyny k nastavení najdete v [dokumentaci k npm provenance](https://docs.npmjs.com/generating-provenance-statements).
|
||||
</Note>
|
||||
|
||||
## Kategorie aplikací
|
||||
## Instalace aplikací
|
||||
|
||||
Twenty organizuje aplikace do tří kategorií podle způsobu distribuce:
|
||||
Jakmile je aplikace publikována (npm) nebo nasazena (tarball), mohou ji pracovní prostory nainstalovat prostřednictvím uživatelského rozhraní.
|
||||
|
||||
| Kategorie | Jak to funguje | Viditelné v Marketplace? |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
||||
| **Vývoj** | Aplikace v místním vývojářském režimu spuštěné přes `yarn twenty dev`. Slouží k sestavování a testování. | Ne |
|
||||
| **Publikováno** | Aplikace publikované na npm s předponou `twenty-app-`. Uvedeny v Marketplace, aby je mohl kterýkoli pracovní prostor nainstalovat. | Ano |
|
||||
| **Interní** | Aplikace nasazené pomocí tarballu na konkrétní server. Dostupné pouze pro pracovní prostory na tomto serveru. | Ne |
|
||||
Přejděte na stránku **Nastavení > Aplikace** v Twenty, kde lze procházet a instalovat jak aplikace z tržiště, tak aplikace nasazené jako tarball.
|
||||
|
||||
<Tip>
|
||||
Začněte v režimu **Development** při sestavování své aplikace. Až bude připravena, zvolte **Published** (npm) pro širokou distribuci nebo **Internal** (tarball) pro soukromé nasazení.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
Aplikace můžete nainstalovat také z příkazového řádku:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
@@ -55,11 +55,12 @@ export const main = async (
|
||||
params: { companyId: string },
|
||||
) => {
|
||||
const { companyId } = params;
|
||||
|
||||
// Replace with your Twenty GraphQL endpoint
|
||||
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
|
||||
// Cloud: https://api.twenty.com/graphql
|
||||
// Self-hosted: https://your-domain.com/graphql
|
||||
const graphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
|
||||
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
// Replace with your API key from Settings → APIs
|
||||
const authToken = 'YOUR_API_KEY';
|
||||
@@ -79,11 +80,40 @@ export const main = async (
|
||||
const pdfBlob = await pdfResponse.blob();
|
||||
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
|
||||
|
||||
// Step 2: Upload the file via GraphQL multipart upload
|
||||
const fieldMetadataIdQuery = `
|
||||
query FindUploadFileFieldMetadataId {
|
||||
objects {
|
||||
edges {
|
||||
node {
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
|
||||
const response = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
query: fieldMetadataIdQuery,
|
||||
}
|
||||
});
|
||||
const result = await response.json();
|
||||
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
|
||||
|
||||
// Step 3: Upload the file via GraphQL multipart upload
|
||||
const uploadMutation = `
|
||||
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
|
||||
uploadFile(file: $file, fileFolder: $fileFolder) {
|
||||
path
|
||||
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
|
||||
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -91,12 +121,12 @@ export const main = async (
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.append('operations', JSON.stringify({
|
||||
query: uploadMutation,
|
||||
variables: { file: null, fileFolder: 'Attachment' },
|
||||
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
|
||||
}));
|
||||
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
|
||||
uploadForm.append('0', pdfFile);
|
||||
|
||||
const uploadResponse = await fetch(graphqlEndpoint, {
|
||||
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
body: uploadForm,
|
||||
@@ -108,15 +138,15 @@ export const main = async (
|
||||
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
|
||||
}
|
||||
|
||||
const filePath = uploadResult.data?.uploadFile?.path;
|
||||
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('No file path returned from upload');
|
||||
if (!fileId) {
|
||||
throw new Error('No file id returned from upload');
|
||||
}
|
||||
|
||||
// Step 3: Create the attachment linked to the company
|
||||
// Step 4: Create the attachment linked to the company
|
||||
const attachmentMutation = `
|
||||
mutation CreateAttachment($data: AttachmentCreateInput!) {
|
||||
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
|
||||
createAttachment(data: $data) {
|
||||
id
|
||||
name
|
||||
@@ -124,7 +154,7 @@ export const main = async (
|
||||
}
|
||||
`;
|
||||
|
||||
const attachmentResponse = await fetch(graphqlEndpoint, {
|
||||
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -135,8 +165,13 @@ export const main = async (
|
||||
variables: {
|
||||
data: {
|
||||
name: filename,
|
||||
fullPath: filePath,
|
||||
companyId,
|
||||
targetCompanyId: companyId,
|
||||
file: [
|
||||
{
|
||||
fileId: fileId,
|
||||
label: filename
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -156,14 +191,14 @@ export const main = async (
|
||||
|
||||
#### Chcete-li připojit k jinému objektu
|
||||
|
||||
Nahraďte `companyId` příslušným polem:
|
||||
Nahraďte `targetCompanyId` příslušným polem:
|
||||
|
||||
| Objekt | Název pole |
|
||||
| -------------- | -------------------- |
|
||||
| Společnost | `companyId` |
|
||||
| Osoba | `personId` |
|
||||
| Příležitost | `opportunityId` |
|
||||
| Vlastní objekt | `yourCustomObjectId` |
|
||||
| Objekt | Název pole |
|
||||
| -------------- | -------------------------- |
|
||||
| Společnost | `targetCompanyId` |
|
||||
| Osoba | `targetPersonId` |
|
||||
| Příležitost | `targetOpportunityId` |
|
||||
| Vlastní objekt | `targetYourCustomObjectId` |
|
||||
|
||||
Aktualizujte jak parametr funkce, tak objekt `variables.data` v mutaci přílohy.
|
||||
|
||||
|
||||
@@ -4,84 +4,142 @@ description: Erstellen Sie in wenigen Minuten Ihre erste Twenty-App.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
|
||||
Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
|
||||
</Warning>
|
||||
|
||||
Apps ermöglichen es Ihnen, Twenty mit benutzerdefinierten Objekten, Feldern, Logikfunktionen, KI-Fähigkeiten und UI-Komponenten zu erweitern — alles als Code verwaltet.
|
||||
|
||||
**Was Sie heute tun können:**
|
||||
|
||||
* Benutzerdefinierte Objekte und Felder als Code definieren (verwaltetes Datenmodell)
|
||||
* Erstellen Sie Logikfunktionen mit benutzerdefinierten Triggern (HTTP-Routen, cron, Datenbankereignisse)
|
||||
* Fähigkeiten für KI-Agenten definieren
|
||||
* Erstellen Sie Frontend-Komponenten, die in der Twenty-UI gerendert werden
|
||||
* Dieselbe App in mehreren Workspaces bereitstellen
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
* Node.js 24+ und Yarn 4
|
||||
* Docker (für den lokalen Twenty-Dev-Server)
|
||||
Bevor Sie beginnen, stellen Sie sicher, dass Folgendes auf Ihrem Rechner installiert ist:
|
||||
|
||||
## Erste Schritte
|
||||
* **Node.js 24+** — [Hier herunterladen](https://nodejs.org/)
|
||||
* **Yarn 4** — Wird mit Node.js über Corepack mitgeliefert. Aktivieren Sie es, indem Sie `corepack enable` ausführen
|
||||
* **Docker** — [Hier herunterladen](https://www.docker.com/products/docker-desktop/). Erforderlich, um eine lokale Twenty-Instanz auszuführen. Nicht erforderlich, wenn bereits ein Twenty-Server läuft.
|
||||
|
||||
Erstellen Sie mit dem offiziellen Scaffolder eine neue App, authentifizieren Sie sich und beginnen Sie mit der Entwicklung:
|
||||
## Schritt 1: App-Gerüst erstellen
|
||||
|
||||
Öffnen Sie ein Terminal und führen Sie Folgendes aus:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
Sie werden aufgefordert, einen Namen und eine Beschreibung für Ihre App einzugeben. Drücken Sie **Enter**, um die Standardwerte zu übernehmen.
|
||||
|
||||
Dadurch wird ein neuer Ordner namens `my-twenty-app` mit allem erstellt, was Sie benötigen.
|
||||
|
||||
<Note>
|
||||
Das Scaffolding-Tool unterstützt diese Flags:
|
||||
|
||||
* `--minimal` — erstellt nur die wesentlichen Dateien, keine Beispiele (Standard)
|
||||
* `--exhaustive` — erstellt alle Beispiel-Entitäten
|
||||
* `--name <name>` — legt den App-Namen fest (überspringt die Abfrage)
|
||||
* `--display-name <displayName>` — legt den Anzeigenamen fest (überspringt die Abfrage)
|
||||
* `--description <description>` — legt die Beschreibung fest (überspringt die Abfrage)
|
||||
* `--skip-local-instance` — überspringt die Eingabeaufforderung zur Einrichtung des lokalen Servers
|
||||
</Note>
|
||||
|
||||
## Schritt 2: Lokale Twenty-Instanz einrichten
|
||||
|
||||
Das Scaffolding-Tool fragt:
|
||||
|
||||
> **Möchten Sie eine lokale Twenty-Instanz einrichten?**
|
||||
|
||||
* **Geben Sie `yes` ein** (empfohlen) — Dadurch wird das Docker-Image `twenty-app-dev` heruntergeladen und ein lokaler Twenty-Server auf Port `2020` gestartet. Stellen Sie sicher, dass Docker läuft, bevor Sie fortfahren.
|
||||
* **Geben Sie `no` ein** — Wählen Sie dies, wenn bereits ein Twenty-Server lokal läuft.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Soll die lokale Instanz gestartet werden?" />
|
||||
</div>
|
||||
|
||||
## Schritt 3: Bei Ihrem Arbeitsbereich anmelden
|
||||
|
||||
Anschließend öffnet sich ein Browserfenster mit der Twenty-Anmeldeseite. Melden Sie sich mit dem vorab eingerichteten Demo-Konto an:
|
||||
|
||||
* **E-Mail:** `tim@apple.dev`
|
||||
* **Passwort:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty-Anmeldebildschirm" />
|
||||
</div>
|
||||
|
||||
## Schritt 4: Die App autorisieren
|
||||
|
||||
Nach der Anmeldung sehen Sie einen Autorisierungsbildschirm. Dadurch kann Ihre App mit Ihrem Arbeitsbereich interagieren.
|
||||
|
||||
Klicken Sie auf **Authorize**, um fortzufahren.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty-CLI-Autorisierungsbildschirm" />
|
||||
</div>
|
||||
|
||||
Nach der Autorisierung bestätigt Ihr Terminal, dass alles eingerichtet ist.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App-Gerüst erfolgreich erstellt" />
|
||||
</div>
|
||||
|
||||
## Schritt 5: Mit der Entwicklung beginnen
|
||||
|
||||
Wechseln Sie in Ihren neuen App-Ordner und starten Sie den Entwicklungsserver:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
Das Scaffolding-Tool unterstützt zwei Modi, um zu steuern, welche Beispieldateien enthalten sind:
|
||||
Dadurch werden Ihre Quelldateien überwacht, bei jeder Änderung neu gebaut und Ihre App automatisch mit dem lokalen Twenty-Server synchronisiert. In Ihrem Terminal sollte eine Live-Statusanzeige angezeigt werden.
|
||||
|
||||
Für ausführlichere Ausgaben (Build-Protokolle, Sync-Anfragen, Fehlerspuren) verwenden Sie das Flag `--verbose`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
Von hier aus können Sie:
|
||||
<Warning>
|
||||
Der Dev-Modus ist nur auf Twenty-Instanzen verfügbar, die im Entwicklungsmodus laufen (`NODE_ENV=development`). Produktionsinstanzen lehnen Dev-Synchronisierungsanfragen ab. Verwenden Sie `yarn twenty deploy`, um auf Produktionsservern bereitzustellen — Details finden Sie unter [Apps veröffentlichen](/l/de/developers/extend/apps/publishing).
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Terminalausgabe im Dev-Modus" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## Schritt 6: Ihre App in Twenty ansehen
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
Öffnen Sie [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in Ihrem Browser. Navigieren Sie zu **Settings > Apps** und wählen Sie die Registerkarte **Developer**. Unter **Your Apps** sollte Ihre App aufgeführt sein:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Liste "Your Apps", die "My twenty app" anzeigt" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
Klicken Sie auf **My twenty app**, um die **Anwendungsregistrierung** zu öffnen. Eine Registrierung ist ein Servereintrag, der Ihre App beschreibt — ihren Namen, den eindeutigen Bezeichner, OAuth-Zugangsdaten und die Quelle (lokal, npm oder Tarball). Sie befindet sich auf dem Server, nicht in einem bestimmten Arbeitsbereich. Wenn Sie eine App in einen Arbeitsbereich installieren, erstellt Twenty eine arbeitsbereichsbezogene Anwendung, die auf diese Registrierung verweist. Eine Registrierung kann in mehreren Arbeitsbereichen auf demselben Server installiert werden.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Details der Anwendungsregistrierung" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
Klicken Sie auf **View installed app**, um die installierte App anzuzeigen. Die Registerkarte **About** zeigt die aktuelle Version und Verwaltungsoptionen:
|
||||
|
||||
Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installierte App — Registerkarte About" />
|
||||
</div>
|
||||
|
||||
## Projektstruktur (vom Scaffolder erzeugt)
|
||||
Wechseln Sie zur Registerkarte **Content**, um alles zu sehen, was Ihre App bereitstellt — Objekte, Felder, Logikfunktionen und Agenten:
|
||||
|
||||
Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der Scaffolder Folgendes:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installierte App — Registerkarte Content" />
|
||||
</div>
|
||||
|
||||
* Kopiert eine minimale Basisanwendung nach `my-twenty-app/`
|
||||
* Fügt eine lokale `twenty-sdk`-Abhängigkeit und die Yarn-4-Konfiguration hinzu
|
||||
* Erstellt Konfigurationsdateien und Skripte, die an die `twenty`-CLI angebunden sind
|
||||
* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Pre-Installations- und Post-Installationsfunktionen) sowie Beispieldateien entsprechend dem Scaffolding-Modus
|
||||
Alles erledigt! Bearbeiten Sie eine beliebige Datei in `src/`, und die Änderungen werden automatisch übernommen.
|
||||
|
||||
Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus:
|
||||
Wechseln Sie zu [Apps erstellen](/l/de/developers/extend/apps/building) für eine ausführliche Anleitung zum Erstellen von Objekten, Logikfunktionen, Frontend-Komponenten, Skills und mehr.
|
||||
|
||||
---
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
Das Scaffolding-Tool erzeugt die folgende Verzeichnisstruktur (gezeigt im Modus `--exhaustive`, der Beispiele für jeden Entitätstyp enthält):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -94,124 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`).
|
||||
Standardmäßig (`--minimal`) werden nur die Kerndateien erstellt: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`. Verwenden Sie `--exhaustive`, um alle oben gezeigten Beispieldateien einzuschließen.
|
||||
|
||||
Auf hoher Ebene:
|
||||
### Wichtige Dateien
|
||||
|
||||
* **package.json**: Deklariert den App-Namen, die Version und die Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie ein `twenty`-Skript hinzu, das an die lokale `twenty`-CLI delegiert. Führen Sie `yarn twenty help` aus, um alle verfügbaren Befehle aufzulisten.
|
||||
* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `generated/` (typisierter Client), `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain.
|
||||
* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest.
|
||||
* **.oxlintrc.json** und **tsconfig.json**: Stellen Linting und TypeScript-Konfiguration für die TypeScript-Quellen Ihrer App bereit.
|
||||
* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen.
|
||||
* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich.
|
||||
* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren
|
||||
| Datei / Ordner | Zweck |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `package.json` | Deklariert den App-Namen, die Version und Abhängigkeiten. Enthält ein `twenty`-Skript, sodass Sie `yarn twenty help` ausführen können, um alle Befehle anzuzeigen. |
|
||||
| `src/application-config.ts` | **Erforderlich.** Die Hauptkonfigurationsdatei für Ihre App. |
|
||||
| `src/roles/` | Definiert Rollen, die steuern, worauf Ihre Logikfunktionen zugreifen können. |
|
||||
| `src/logic-functions/` | Serverseitige Funktionen, die durch Routen, Cron-Zeitpläne oder Datenbankereignisse ausgelöst werden. |
|
||||
| `src/front-components/` | React-Komponenten, die innerhalb der Twenty-UI gerendert werden. |
|
||||
| `src/objects/` | Benutzerdefinierte Objektdefinitionen zur Erweiterung Ihres Datenmodells. |
|
||||
| `src/fields/` | Benutzerdefinierte Felder, die vorhandenen Objekten hinzugefügt werden. |
|
||||
| `src/views/` | Konfigurationen gespeicherter Ansichten. |
|
||||
| `src/navigation-menu-items/` | Benutzerdefinierte Links in der Seitenleisten-Navigation. |
|
||||
| `src/skills/` | Skills, die die KI-Agenten von Twenty erweitern. |
|
||||
| `src/agents/` | KI-Agenten mit benutzerdefinierten Prompts. |
|
||||
| `src/page-layouts/` | Benutzerdefinierte Seitenlayouts für Datensatzansichten. |
|
||||
| `src/__tests__/` | Integrationstests (Setup + Beispieltest). |
|
||||
| `public/` | Statische Assets (Bilder, Schriftarten), die mit Ihrer App ausgeliefert werden. |
|
||||
|
||||
### Entitätserkennung
|
||||
## Remotes verwalten
|
||||
|
||||
Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define<Entity>({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird:
|
||||
|
||||
| Hilfsfunktion | Entitätstyp |
|
||||
| -------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `defineObject` | Benutzerdefinierte Objektdefinitionen |
|
||||
| `defineLogicFunction` | Definitionen von Logikfunktionen |
|
||||
| `definePreInstallLogicFunction` | Pre-Installations-Logikfunktion (wird vor der Installation ausgeführt) |
|
||||
| `definePostInstallLogicFunction` | Post-Installations-Logikfunktion (wird nach der Installation ausgeführt) |
|
||||
| `defineFrontComponent` | Definitionen von Frontend-Komponenten |
|
||||
| `defineRole` | Rollendefinitionen |
|
||||
| `defineField` | Felderweiterungen für bestehende Objekte |
|
||||
| `defineView` | Gespeicherte View-Definitionen |
|
||||
| `defineNavigationMenuItem` | Definitionen von Navigationsmenüeinträgen |
|
||||
| `defineSkill` | Skill-Definitionen für KI-Agenten |
|
||||
|
||||
<Note>
|
||||
**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define<Entity>({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung.
|
||||
</Note>
|
||||
|
||||
Beispiel für eine erkannte Entität:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Spätere Befehle fügen weitere Dateien und Ordner hinzu:
|
||||
|
||||
* `yarn twenty dev` generiert automatisch zwei typisierte API-Clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (für Arbeitsbereichsdaten über `/graphql`) und `MetadataApiClient` (für Arbeitsbereichskonfiguration und Datei-Uploads über `/metadata`).
|
||||
* `yarn twenty entity:add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Frontend-Komponenten, Rollen, Skills und mehr hinzu.
|
||||
|
||||
## Authentifizierung
|
||||
|
||||
Wenn Sie `yarn twenty auth:login` zum ersten Mal ausführen, werden Sie nach Folgendem gefragt:
|
||||
|
||||
* API-URL (standardmäßig http://localhost:3000 oder Ihr aktuelles Workspace-Profil)
|
||||
* API-Schlüssel
|
||||
|
||||
Ihre Anmeldedaten werden pro Benutzer in `~/.twenty/config.json` gespeichert. Sie können mehrere Profile verwalten und zwischen ihnen wechseln.
|
||||
|
||||
### Arbeitsbereiche verwalten
|
||||
Ein **Remote** ist ein Twenty-Server, mit dem sich Ihre App verbindet. Während der Einrichtung erstellt das Scaffolding-Tool automatisch eines für Sie. Sie können jederzeit weitere Remotes hinzufügen oder zwischen ihnen wechseln.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Sobald Sie mit `yarn twenty auth:switch` den Arbeitsbereich gewechselt haben, verwenden alle nachfolgenden Befehle standardmäßig diesen Arbeitsbereich. Sie können es weiterhin vorübergehend mit `--workspace <name>` überschreiben.
|
||||
Ihre Anmeldedaten werden in `~/.twenty/config.json` gespeichert.
|
||||
|
||||
## Lokaler Entwicklungsserver (`yarn twenty server`)
|
||||
|
||||
Die CLI kann einen lokalen, in Docker laufenden Twenty-Server verwalten. Dies ist derselbe Server, der automatisch gestartet wird, wenn Sie mit `create-twenty-app` eine App aufsetzen, aber Sie können ihn auch manuell verwalten.
|
||||
|
||||
### Server starten
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
Dadurch wird das Docker-Image `twentycrm/twenty-app-dev:latest` heruntergeladen (falls nicht bereits vorhanden), ein Container namens `twenty-app-dev` erstellt und auf Port **2020** gestartet. Die CLI wartet, bis der Server seinen Health-Check bestanden hat, bevor sie zurückkehrt.
|
||||
|
||||
Es werden zwei Docker-Volumes erstellt, um Daten zwischen Neustarts beizubehalten:
|
||||
|
||||
* `twenty-app-dev-data` — PostgreSQL-Datenbank
|
||||
* `twenty-app-dev-storage` — Dateispeicher
|
||||
|
||||
Wenn Port 2020 bereits verwendet wird, können Sie auf einem anderen Port starten:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
Die CLI konfiguriert automatisch die internen Werte `NODE_PORT` und `SERVER_URL` des Containers passend zum gewählten Port, sodass Logikfunktionen, OAuth und alle anderen internen Netzwerkfunktionen korrekt arbeiten.
|
||||
|
||||
Nach dem Start wird der Server automatisch als `local`-Remote in Ihrer CLI-Konfiguration registriert.
|
||||
|
||||
### Serverstatus prüfen
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
Zeigt an, ob der Server läuft, seine URL und die Standard-Anmeldedaten (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### Serverprotokolle anzeigen
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
Streamt die Containerprotokolle. Verwenden Sie `--lines`, um zu steuern, wie viele der letzten Zeilen angezeigt werden:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### Server stoppen
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
Stoppt den Container. Ihre Daten bleiben in den Docker-Volumes erhalten — der nächste `start` macht dort weiter, wo Sie aufgehört haben.
|
||||
|
||||
### Server zurücksetzen
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
Entfernt den Container **und** löscht beide Docker-Volumes, wobei alle Daten gelöscht werden. Der nächste `start` erstellt eine frische Instanz.
|
||||
|
||||
<Note>
|
||||
Der Server erfordert, dass **Docker** läuft. Wenn der Fehler "Docker not running" angezeigt wird, stellen Sie sicher, dass Docker Desktop (oder der Docker-Daemon) gestartet ist.
|
||||
</Note>
|
||||
|
||||
### Befehlsreferenz
|
||||
|
||||
| Befehl | Beschreibung |
|
||||
| -------------------------------------- | ----------------------------------------------------------- |
|
||||
| `yarn twenty server start` | Lokalen Server starten (lädt das Image bei Bedarf herunter) |
|
||||
| `yarn twenty server start --port 3030` | Auf einem benutzerdefinierten Port starten |
|
||||
| `yarn twenty server stop` | Server stoppen (Daten bleiben erhalten) |
|
||||
| `yarn twenty server status` | Serverstatus, URL und Anmeldedaten anzeigen |
|
||||
| `yarn twenty server logs` | Serverprotokolle streamen |
|
||||
| `yarn twenty server logs --lines 100` | Die letzten 100 Protokollzeilen anzeigen |
|
||||
| `yarn twenty server reset` | Alle Daten löschen und neu starten |
|
||||
|
||||
## CI mit GitHub Actions
|
||||
|
||||
Das Scaffolding-Tool erzeugt einen einsatzbereiten GitHub-Actions-Workflow in `.github/workflows/ci.yml`. Er führt Ihre Integrationstests automatisch bei jedem Push auf `main` und bei Pull Requests aus.
|
||||
|
||||
Der Workflow:
|
||||
|
||||
1. Checkt Ihren Code aus
|
||||
2. Startet einen temporären Twenty-Server mit der Aktion `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
|
||||
3. Installiert Abhängigkeiten mit `yarn install --immutable`
|
||||
4. Führt `yarn test` aus, wobei `TWENTY_API_URL` und `TWENTY_API_KEY` aus den Aktionsausgaben injiziert werden.
|
||||
|
||||
```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 }}
|
||||
```
|
||||
|
||||
Sie müssen keine Secrets konfigurieren — die Aktion `spawn-twenty-docker-image` startet einen flüchtigen Twenty-Server direkt im Runner und gibt die Verbindungsdetails aus. Das Secret `GITHUB_TOKEN` wird automatisch von GitHub bereitgestellt.
|
||||
|
||||
Um eine bestimmte Twenty-Version statt `latest` festzulegen, ändern Sie die Umgebungsvariable `TWENTY_VERSION` oben im Workflow.
|
||||
|
||||
## Manuelle Einrichtung (ohne Scaffolder)
|
||||
|
||||
Wir empfehlen zwar `create-twenty-app` für das beste Einstiegserlebnis, Sie können ein Projekt aber auch manuell einrichten. Installieren Sie die CLI nicht global. Fügen Sie stattdessen `twenty-sdk` als lokale Abhängigkeit hinzu und binden Sie ein einzelnes Skript in Ihrer package.json ein:
|
||||
Wenn Sie die Einrichtung lieber selbst vornehmen möchten, anstatt `create-twenty-app` zu verwenden, können Sie dies in zwei Schritten tun.
|
||||
|
||||
**1. Fügen Sie `twenty-sdk` und `twenty-client-sdk` als Abhängigkeiten hinzu:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Fügen Sie dann ein `twenty`-Skript hinzu:
|
||||
**2. Fügen Sie Ihrer `package.json` ein `twenty`-Skript hinzu:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -221,25 +393,19 @@ Fügen Sie dann ein `twenty`-Skript hinzu:
|
||||
}
|
||||
```
|
||||
|
||||
Jetzt können Sie alle Befehle über `yarn twenty <command>` ausführen, z. B. `yarn twenty dev`, `yarn twenty help` usw.
|
||||
Sie können jetzt `yarn twenty dev`, `yarn twenty help` und alle anderen Befehle ausführen.
|
||||
|
||||
## So verwenden Sie eine lokale Twenty-Instanz
|
||||
|
||||
Wenn Sie bereits lokal eine Twenty-Instanz ausführen (z. B. über `npx nx start twenty-server`), können Sie sich damit verbinden, anstatt Docker zu verwenden:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
Installieren Sie `twenty-sdk` nicht global. Verwenden Sie es immer als lokale Projektabhängigkeit, damit jedes Projekt seine eigene Version festlegen kann.
|
||||
</Note>
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
* Authentifizierungsfehler: Führen Sie `yarn twenty auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat.
|
||||
* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist.
|
||||
* Typen oder Client fehlen oder sind veraltet: Starten Sie `yarn twenty dev` neu — der typisierte Client wird automatisch generiert.
|
||||
* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn twenty dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden.
|
||||
Wenn Probleme auftreten:
|
||||
|
||||
Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
* Stellen Sie sicher, dass **Docker läuft**, bevor Sie das Scaffolding-Tool mit einer lokalen Instanz starten.
|
||||
* Stellen Sie sicher, dass Sie **Node.js 24+** verwenden (`node -v` zur Überprüfung).
|
||||
* Stellen Sie sicher, dass **Corepack aktiviert ist** (`corepack enable`), damit Yarn 4 verfügbar ist.
|
||||
* Versuchen Sie, `node_modules` zu löschen und `yarn install` erneut auszuführen, wenn Abhängigkeiten fehlerhaft erscheinen.
|
||||
|
||||
Hängen Sie immer noch fest? Bitten Sie im [Twenty-Discord](https://discord.com/channels/1130383047699738754/1130386664812982322) um Hilfe.
|
||||
|
||||
@@ -4,15 +4,75 @@ description: Veröffentlichen Sie Ihre Twenty-App auf dem Twenty-Marktplatz oder
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
|
||||
Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
|
||||
</Warning>
|
||||
|
||||
## Übersicht
|
||||
|
||||
Sobald Ihre App [lokal gebaut und getestet](/l/de/developers/extend/apps/building) wurde, haben Sie zwei Möglichkeiten, sie zu verteilen:
|
||||
|
||||
* **Einen Tarball bereitstellen** — Laden Sie Ihre App direkt auf einen bestimmten Twenty-Server für die interne oder private Nutzung hoch.
|
||||
* **Auf npm veröffentlichen** — führen Sie Ihre App im Twenty-Marktplatz auf, damit jeder Arbeitsbereich sie entdecken und installieren kann.
|
||||
* **Einen Tarball pushen** — stellen Sie Ihre App auf einem bestimmten Twenty-Server für die interne Nutzung bereit, ohne sie öffentlich verfügbar zu machen.
|
||||
|
||||
Beide Pfade beginnen mit demselben **Build**-Schritt.
|
||||
|
||||
## Erstellen Ihrer App
|
||||
|
||||
Führen Sie den Build-Befehl aus, um Ihre App zu kompilieren und eine distributionsfertige `manifest.json` zu erzeugen:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
Dabei werden TypeScript-Quelltexte kompiliert, Logikfunktionen und Frontend-Komponenten transpiliert und alles in `.twenty/output/` geschrieben. Fügen Sie `--tarball` hinzu, um zusätzlich ein `.tgz`-Paket für die manuelle Verteilung oder den Deploy-Befehl zu erzeugen.
|
||||
|
||||
## Bereitstellung auf einem Server (Tarball)
|
||||
|
||||
Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, ausschließlich für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einem Twenty-Server bereitstellen.
|
||||
|
||||
### Voraussetzungen
|
||||
|
||||
Bevor Sie bereitstellen, benötigen Sie ein konfiguriertes Remote, das auf den Zielserver zeigt. Remotes speichern die Server-URL und Anmeldeinformationen lokal in `~/.twenty/config.json`.
|
||||
|
||||
Ein Remote hinzufügen:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Bereitstellen
|
||||
|
||||
Bauen und laden Sie Ihre App in einem Schritt auf den Server hoch:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Eine bereitgestellte App freigeben
|
||||
|
||||
Tarball-Apps werden nicht im öffentlichen Marktplatz gelistet, daher entdecken andere Arbeitsbereiche auf demselben Server sie nicht durch Stöbern. So geben Sie eine bereitgestellte App frei:
|
||||
|
||||
1. Gehen Sie zu **Einstellungen > Anwendungen > Registrierungen** und öffnen Sie Ihre App
|
||||
2. Klicken Sie im Tab **Distribution** auf **Freigabelink kopieren**
|
||||
3. Teilen Sie diesen Link mit Nutzern in anderen Arbeitsbereichen — er führt sie direkt zur Installationsseite der App
|
||||
|
||||
Der Freigabelink verwendet die Basis-URL des Servers (ohne Workspace-Subdomain), sodass er für jeden Arbeitsbereich auf dem Server funktioniert.
|
||||
|
||||
<Warning>
|
||||
Das Teilen privater Apps ist eine Enterprise-Funktion. Gehen Sie zu [Einstellungen > Admin-Panel > Enterprise](/settings/admin-panel#enterprise), um es zu aktivieren.
|
||||
</Warning>
|
||||
|
||||
### Versionsverwaltung
|
||||
|
||||
So veröffentlichen Sie ein Update:
|
||||
|
||||
1. Erhöhen Sie das Feld `version` in Ihrer `package.json`
|
||||
2. Führen Sie `yarn twenty deploy` aus (oder `yarn twenty deploy --remote production`)
|
||||
3. Arbeitsbereiche, die die App installiert haben, sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist.
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## Auf npm veröffentlichen
|
||||
|
||||
@@ -21,29 +81,69 @@ Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Je
|
||||
### Anforderungen
|
||||
|
||||
* Ein [npm](https://www.npmjs.com)-Konto
|
||||
* Ihr Paketname **muss** das Präfix `twenty-app-` verwenden (z. B. `twenty-app-postcard-sender`)
|
||||
* Das Schlüsselwort `twenty-app` in Ihrem `package.json`-Array `keywords` (bereits enthalten, wenn Sie mit `create-twenty-app` ein Gerüst erstellen)
|
||||
|
||||
### Schritte
|
||||
|
||||
1. **App erstellen** — die CLI kompiliert Ihre TypeScript-Quellen und erzeugt das Anwendungsmanifest:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Auf npm veröffentlichen** — pushen Sie das gebaute Paket in die npm-Registry:
|
||||
### Marktplatz-Metadaten
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
Die `defineApplication()`-Konfiguration unterstützt optionale Felder, die steuern, wie Ihre App im Marktplatz erscheint. Verwenden Sie `logoUrl` und `screenshots`, um Bilder aus dem Ordner `public/` zu referenzieren:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Automatische Erkennung
|
||||
Siehe das [defineApplication-Akkordeon](/l/de/developers/extend/apps/building#defineentity-functions) auf der Seite Building Apps für die vollständige Liste der Marktplatzfelder (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` usw.).
|
||||
|
||||
Pakete mit dem Präfix `twenty-app-` werden vom Twenty-Marktplatzkatalog automatisch erkannt. Nach der Veröffentlichung erscheint Ihre App innerhalb weniger Minuten im Marktplatz — keine manuelle Registrierung oder Genehmigung erforderlich.
|
||||
### Veröffentlichen
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
Um unter einem bestimmten dist-tag zu veröffentlichen (z. B. `beta` oder `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### So funktioniert die Marktplatz-Erkennung
|
||||
|
||||
Der Twenty-Server synchronisiert seinen Marktplatzkatalog **stündlich** aus der npm-Registry.
|
||||
|
||||
Sie können die Synchronisierung sofort auslösen, anstatt zu warten:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
Die im Marktplatz angezeigten Metadaten stammen aus Ihrer `defineApplication()`-Konfiguration — Felder wie `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` und `termsUrl`.
|
||||
|
||||
<Note>
|
||||
Wenn deine App keine `aboutDescription` in `defineApplication()` definiert, verwendet der Marktplatz automatisch die `README.md` deines Pakets von npm als Inhalt der Über-uns-Seite. Das bedeutet, dass du eine einzige README sowohl für npm als auch für den Twenty-Marktplatz pflegen kannst. Wenn du im Marktplatz eine andere Beschreibung möchtest, setze `aboutDescription` explizit.
|
||||
</Note>
|
||||
|
||||
### CI-Veröffentlichung
|
||||
|
||||
Das vorgefertigte Projekt enthält einen GitHub-Actions-Workflow, der bei jedem Release eine Veröffentlichung durchführt. Er führt `app:build` aus und danach `npm publish --provenance` aus dem Build-Output:
|
||||
Verwenden Sie diesen GitHub-Actions-Workflow, um bei jedem Release automatisch zu veröffentlichen (verwendet [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `npx twenty build` und anschließend `npm publish` aus `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**npm-Provenance** ist optional, wird jedoch empfohlen. Das Veröffentlichen mit `--provenance` fügt Ihrem npm-Eintrag ein Vertrauensabzeichen hinzu, sodass Nutzer überprüfen können, dass das Paket aus einem bestimmten Commit in einer öffentlichen CI-Pipeline gebaut wurde. Siehe die [npm-Provenance-Dokumentation](https://docs.npmjs.com/generating-provenance-statements) für Einrichtungshinweise.
|
||||
</Tip>
|
||||
|
||||
## Interne Verteilung
|
||||
|
||||
Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, nur für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einen Twenty-Server pushen.
|
||||
|
||||
### Einen Tarball pushen
|
||||
|
||||
Erstellen Sie Ihre App und stellen Sie sie in einem Schritt auf einem bestimmten Server bereit:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
Jeder Arbeitsbereich auf diesem Server kann die App anschließend über die Seite **Applications** in den Einstellungen installieren und aktualisieren.
|
||||
|
||||
### Versionsverwaltung
|
||||
|
||||
So veröffentlichen Sie ein Update:
|
||||
|
||||
1. Erhöhen Sie das Feld `version` in Ihrer `package.json`
|
||||
2. Pushen Sie einen neuen Tarball mit `npx twenty publish --server <server-url>`
|
||||
3. Arbeitsbereiche auf diesem Server sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist.
|
||||
Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `yarn twenty build` und anschließend `npm publish` aus `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
Interne Apps sind auf den Server beschränkt, auf den sie gepusht werden. Sie erscheinen nicht im öffentlichen Marktplatz und können von Arbeitsbereichen auf anderen Servern nicht installiert werden.
|
||||
**npm-Provenance** ist optional, wird jedoch empfohlen. Das Veröffentlichen mit `--provenance` fügt Ihrem npm-Eintrag ein Vertrauensabzeichen hinzu, sodass Nutzer überprüfen können, dass das Paket aus einem bestimmten Commit in einer öffentlichen CI-Pipeline gebaut wurde. Siehe die [npm-Provenance-Dokumentation](https://docs.npmjs.com/generating-provenance-statements) für Einrichtungshinweise.
|
||||
</Note>
|
||||
|
||||
## App-Kategorien
|
||||
## Apps installieren
|
||||
|
||||
Twenty organisiert Apps in drei Kategorien, basierend auf ihrer Vertriebsart:
|
||||
Sobald eine App veröffentlicht (npm) oder bereitgestellt (Tarball) wurde, können Arbeitsbereiche sie über die Benutzeroberfläche installieren.
|
||||
|
||||
| Kategorie | Wie es funktioniert | Im Marktplatz sichtbar? |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
|
||||
| **Entwicklung** | Lokale Apps im Entwicklungsmodus, die über `yarn twenty dev` ausgeführt werden. Zum Erstellen und Testen verwendet. | Nein |
|
||||
| **Veröffentlicht** | Auf npm veröffentlichte Apps mit dem Präfix `twenty-app-`. Im Marktplatz gelistet, damit jeder Arbeitsbereich sie installieren kann. | Ja |
|
||||
| **Intern** | Apps, die per Tarball auf einen bestimmten Server bereitgestellt werden. Nur für Arbeitsbereiche auf diesem Server verfügbar. | Nein |
|
||||
Gehen Sie zur Seite **Einstellungen > Anwendungen** in Twenty, auf der sowohl Marktplatz- als auch per Tarball bereitgestellte Apps durchsucht und installiert werden können.
|
||||
|
||||
<Tip>
|
||||
Beginnen Sie im **Entwicklungsmodus**, während Sie Ihre App erstellen. Wenn sie bereit ist, wählen Sie **Veröffentlicht** (npm) für die breite Verteilung oder **Intern** (Tarball) für die private Bereitstellung.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
Sie können Apps auch über die Befehlszeile installieren:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
@@ -55,11 +55,12 @@ export const main = async (
|
||||
params: { companyId: string },
|
||||
) => {
|
||||
const { companyId } = params;
|
||||
|
||||
// Replace with your Twenty GraphQL endpoint
|
||||
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
|
||||
// Cloud: https://api.twenty.com/graphql
|
||||
// Self-hosted: https://your-domain.com/graphql
|
||||
const graphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
|
||||
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
// Replace with your API key from Settings → APIs
|
||||
const authToken = 'YOUR_API_KEY';
|
||||
@@ -79,11 +80,40 @@ export const main = async (
|
||||
const pdfBlob = await pdfResponse.blob();
|
||||
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
|
||||
|
||||
// Step 2: Upload the file via GraphQL multipart upload
|
||||
const fieldMetadataIdQuery = `
|
||||
query FindUploadFileFieldMetadataId {
|
||||
objects {
|
||||
edges {
|
||||
node {
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
|
||||
const response = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
query: fieldMetadataIdQuery,
|
||||
}
|
||||
});
|
||||
const result = await response.json();
|
||||
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
|
||||
|
||||
// Step 3: Upload the file via GraphQL multipart upload
|
||||
const uploadMutation = `
|
||||
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
|
||||
uploadFile(file: $file, fileFolder: $fileFolder) {
|
||||
path
|
||||
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
|
||||
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -91,12 +121,12 @@ export const main = async (
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.append('operations', JSON.stringify({
|
||||
query: uploadMutation,
|
||||
variables: { file: null, fileFolder: 'Attachment' },
|
||||
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
|
||||
}));
|
||||
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
|
||||
uploadForm.append('0', pdfFile);
|
||||
|
||||
const uploadResponse = await fetch(graphqlEndpoint, {
|
||||
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
body: uploadForm,
|
||||
@@ -108,15 +138,15 @@ export const main = async (
|
||||
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
|
||||
}
|
||||
|
||||
const filePath = uploadResult.data?.uploadFile?.path;
|
||||
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('No file path returned from upload');
|
||||
if (!fileId) {
|
||||
throw new Error('No file id returned from upload');
|
||||
}
|
||||
|
||||
// Step 3: Create the attachment linked to the company
|
||||
// Step 4: Create the attachment linked to the company
|
||||
const attachmentMutation = `
|
||||
mutation CreateAttachment($data: AttachmentCreateInput!) {
|
||||
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
|
||||
createAttachment(data: $data) {
|
||||
id
|
||||
name
|
||||
@@ -124,7 +154,7 @@ export const main = async (
|
||||
}
|
||||
`;
|
||||
|
||||
const attachmentResponse = await fetch(graphqlEndpoint, {
|
||||
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -135,8 +165,13 @@ export const main = async (
|
||||
variables: {
|
||||
data: {
|
||||
name: filename,
|
||||
fullPath: filePath,
|
||||
companyId,
|
||||
targetCompanyId: companyId,
|
||||
file: [
|
||||
{
|
||||
fileId: fileId,
|
||||
label: filename
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -156,14 +191,14 @@ export const main = async (
|
||||
|
||||
#### An ein anderes Objekt anhängen
|
||||
|
||||
Ersetzen Sie `companyId` durch das entsprechende Feld:
|
||||
Ersetzen Sie `targetCompanyId` durch das entsprechende Feld:
|
||||
|
||||
| Objekt | Feldname |
|
||||
| -------------------------- | -------------------- |
|
||||
| Unternehmen | `companyId` |
|
||||
| Person | `personId` |
|
||||
| Opportunity | `opportunityId` |
|
||||
| Benutzerdefiniertes Objekt | `yourCustomObjectId` |
|
||||
| Objekt | Feldname |
|
||||
| -------------------------- | -------------------------- |
|
||||
| Unternehmen | `targetCompanyId` |
|
||||
| Person | `targetPersonId` |
|
||||
| Opportunity | `targetOpportunityId` |
|
||||
| Benutzerdefiniertes Objekt | `targetYourCustomObjectId` |
|
||||
|
||||
Aktualisieren Sie sowohl den Funktionsparameter als auch das Objekt `variables.data` in der Attachment-Mutation.
|
||||
|
||||
|
||||
@@ -4,84 +4,142 @@ description: Crea la tua prima app Twenty in pochi minuti.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.
|
||||
Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione.
|
||||
</Warning>
|
||||
|
||||
Le app ti permettono di estendere Twenty con oggetti, campi, funzioni logiche, competenze IA e componenti UI personalizzati — il tutto gestito come codice.
|
||||
|
||||
**Cosa puoi fare oggi:**
|
||||
|
||||
* Definisci oggetti e campi personalizzati come codice (modello dati gestito)
|
||||
* Crea funzioni logiche con trigger personalizzati (route HTTP, cron, eventi del database)
|
||||
* Definisci le abilità per gli agenti IA
|
||||
* Crea componenti front-end che vengono renderizzati all'interno della UI di Twenty
|
||||
* Distribuisci la stessa app su più spazi di lavoro
|
||||
|
||||
## Prerequisiti
|
||||
|
||||
* Node.js 24+ e Yarn 4
|
||||
* Docker (per il server di sviluppo locale di Twenty)
|
||||
Prima di iniziare, assicurati che quanto segue sia installato sul tuo computer:
|
||||
|
||||
## Per iniziare
|
||||
* **Node.js 24+** — [Scarica qui](https://nodejs.org/)
|
||||
* **Yarn 4** — Incluso con Node.js tramite Corepack. Abilitalo eseguendo `corepack enable`
|
||||
* **Docker** — [Scarica qui](https://www.docker.com/products/docker-desktop/). Necessario per eseguire un'istanza locale di Twenty. Non necessario se hai già un server Twenty in esecuzione.
|
||||
|
||||
Crea una nuova app utilizzando lo scaffolder ufficiale, quindi autenticati e inizia a sviluppare:
|
||||
## Passaggio 1: Crea lo scheletro della tua app
|
||||
|
||||
Apri un terminale ed esegui:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
Ti verrà chiesto di inserire un nome e una descrizione per la tua app. Premi **Invio** per accettare i valori predefiniti.
|
||||
|
||||
Questo crea una nuova cartella chiamata `my-twenty-app` con tutto il necessario.
|
||||
|
||||
<Note>
|
||||
Lo strumento di scaffolding supporta questi flag:
|
||||
|
||||
* `--minimal` — genera solo i file essenziali, senza esempi (predefinito)
|
||||
* `--exhaustive` — genera tutte le entità di esempio
|
||||
* `--name <name>` — imposta il nome dell'app (salta la richiesta)
|
||||
* `--display-name <displayName>` — imposta il nome visualizzato (salta la richiesta)
|
||||
* `--description <description>` — imposta la descrizione (salta la richiesta)
|
||||
* `--skip-local-instance` — salta la richiesta di configurazione del server locale
|
||||
</Note>
|
||||
|
||||
## Passaggio 2: Configura un'istanza locale di Twenty
|
||||
|
||||
Lo strumento di scaffolding chiederà:
|
||||
|
||||
> **Vuoi configurare un'istanza locale di Twenty?**
|
||||
|
||||
* **Digita `yes`** (consigliato) — Questo scarica l'immagine Docker `twenty-app-dev` e avvia un server Twenty locale sulla porta `2020`. Assicurati che Docker sia in esecuzione prima di continuare.
|
||||
* **Digita `no`** — Sceglilo se hai già un server Twenty in esecuzione in locale.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Avviare l'istanza locale?" />
|
||||
</div>
|
||||
|
||||
## Passaggio 3: Accedi al tuo spazio di lavoro
|
||||
|
||||
Successivamente si aprirà una finestra del browser con la pagina di accesso di Twenty. Accedi con l'account demo preconfigurato:
|
||||
|
||||
* **Email:** `tim@apple.dev`
|
||||
* **Password:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Schermata di accesso di Twenty" />
|
||||
</div>
|
||||
|
||||
## Passaggio 4: Autorizza l'app
|
||||
|
||||
Dopo l'accesso, vedrai una schermata di autorizzazione. Questo consente alla tua app di interagire con il tuo spazio di lavoro.
|
||||
|
||||
Fai clic su **Authorize** per continuare.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Schermata di autorizzazione della CLI di Twenty" />
|
||||
</div>
|
||||
|
||||
Una volta autorizzato, il terminale confermerà che tutto è configurato.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App creata con successo" />
|
||||
</div>
|
||||
|
||||
## Passaggio 5: Inizia a sviluppare
|
||||
|
||||
Entra nella nuova cartella della tua app e avvia il server di sviluppo:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
Lo strumento di scaffolding supporta due modalità per controllare quali file di esempio vengono inclusi:
|
||||
Questo controlla i file sorgente, ricompila a ogni modifica e sincronizza automaticamente la tua app con il server Twenty locale. Dovresti vedere un pannello di stato in tempo reale nel terminale.
|
||||
|
||||
Per un output più dettagliato (log di build, richieste di sincronizzazione, tracce di errore), usa il flag `--verbose`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
Da qui puoi:
|
||||
<Warning>
|
||||
La modalità di sviluppo è disponibile solo sulle istanze di Twenty in esecuzione in modalità sviluppo (`NODE_ENV=development`). Le istanze di produzione rifiutano le richieste di sincronizzazione in modalità sviluppo. Usa `yarn twenty deploy` per distribuire sui server di produzione — vedi [Pubblicazione delle app](/l/it/developers/extend/apps/publishing) per i dettagli.
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Output del terminale in modalità sviluppo" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## Passaggio 6: Visualizza la tua app in Twenty
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
Apri [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) nel browser. Vai su **Settings > Apps** e seleziona la scheda **Developer**. Dovresti vedere la tua app elencata in **Your Apps**:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Elenco Your Apps che mostra My twenty app" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
Fai clic su **My twenty app** per aprire la sua **registrazione dell'applicazione**. Una registrazione è un record a livello di server che descrive la tua app — il suo nome, identificatore univoco, credenziali OAuth e origine (locale, npm o tarball). Risiede sul server, non all'interno di uno spazio di lavoro specifico. Quando installi un'app in uno spazio di lavoro, Twenty crea un'**applicazione** con ambito dello spazio di lavoro che rimanda a questa registrazione. Una registrazione può essere installata in più spazi di lavoro sullo stesso server.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Dettagli della registrazione dell'applicazione" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
Fai clic su **View installed app** per vedere l'app installata. La scheda **About** mostra la versione corrente e le opzioni di gestione:
|
||||
|
||||
Vedi anche: le pagine di riferimento della CLI per [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="App installata — scheda About" />
|
||||
</div>
|
||||
|
||||
## Struttura del progetto (generata dallo scaffolder)
|
||||
Passa alla scheda **Content** per vedere tutto ciò che la tua app fornisce — oggetti, campi, funzioni logiche e agenti:
|
||||
|
||||
Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="App installata — scheda Content" />
|
||||
</div>
|
||||
|
||||
* Copia un'applicazione base minimale in `my-twenty-app/`
|
||||
* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4
|
||||
* Crea file di configurazione e script collegati alla CLI `twenty`
|
||||
* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzioni di pre-installazione e post-installazione) più i file di esempio in base alla modalità di scaffolding
|
||||
È tutto pronto! Modifica qualsiasi file in `src/` e le modifiche verranno rilevate automaticamente.
|
||||
|
||||
Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così:
|
||||
Vai a [Creare app](/l/it/developers/extend/apps/building) per una guida dettagliata sulla creazione di oggetti, funzioni logiche, componenti front-end, skill e altro.
|
||||
|
||||
---
|
||||
|
||||
## Struttura del progetto
|
||||
|
||||
Lo strumento di scaffolding genera la seguente struttura di file (mostrata con la modalità `--exhaustive`, che include esempi per ogni tipo di entità):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -94,124 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
|
||||
Per impostazione predefinita (`--minimal`), vengono creati solo i file principali: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`. Usa `--exhaustive` per includere tutti i file di esempio mostrati sopra.
|
||||
|
||||
A livello generale:
|
||||
### File principali
|
||||
|
||||
* **package.json**: Dichiara il nome dell'app, la versione, i motori (Node 24+, Yarn 4) e aggiunge `twenty-sdk` più uno script `twenty` che delega alla CLI locale `twenty`. Esegui `yarn twenty help` per elencare tutti i comandi disponibili.
|
||||
* **.gitignore**: Ignora i file generati comuni come `node_modules`, `.yarn`, `generated/` (client tipizzato), `dist/`, `build/`, cartelle di coverage, file di log e file `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloccano e configurano la toolchain Yarn 4 utilizzata dal progetto.
|
||||
* **.nvmrc**: Fissa la versione di Node.js prevista dal progetto.
|
||||
* **.oxlintrc.json** e **tsconfig.json**: Forniscono linting e configurazione TypeScript per i sorgenti TypeScript della tua app.
|
||||
* **README.md**: Un breve README nella radice dell'app con istruzioni di base.
|
||||
* **public/**: Una cartella per archiviare risorse pubbliche (immagini, font, file statici) che saranno servite con la tua applicazione. I file collocati qui vengono caricati durante la sincronizzazione e sono accessibili in fase di esecuzione.
|
||||
* **src/**: Il luogo principale in cui definisci la tua applicazione come codice
|
||||
| File / Cartella | Scopo |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `package.json` | Dichiara il nome, la versione e le dipendenze della tua app. Include uno script `twenty` così puoi eseguire `yarn twenty help` per vedere tutti i comandi. |
|
||||
| `src/application-config.ts` | **Obbligatorio.** Il file di configurazione principale della tua app. |
|
||||
| `src/roles/` | Definisce i ruoli che controllano a cosa possono accedere le tue funzioni logiche. |
|
||||
| `src/logic-functions/` | Funzioni lato server attivate da route, pianificazioni cron o eventi del database. |
|
||||
| `src/front-components/` | Componenti React che vengono renderizzati all'interno della UI di Twenty. |
|
||||
| `src/objects/` | Definizioni di oggetti personalizzati per estendere il tuo modello dati. |
|
||||
| `src/fields/` | Campi personalizzati aggiunti a oggetti esistenti. |
|
||||
| `src/views/` | Configurazioni di viste salvate. |
|
||||
| `src/navigation-menu-items/` | Link personalizzati nella navigazione laterale. |
|
||||
| `src/skills/` | Abilità che estendono gli agenti IA di Twenty. |
|
||||
| `src/agents/` | Agenti IA con prompt personalizzati. |
|
||||
| `src/page-layouts/` | Layout di pagina personalizzati per le viste dei record. |
|
||||
| `src/__tests__/` | Test di integrazione (setup + test di esempio). |
|
||||
| `public/` | Asset statici (immagini, font) serviti insieme alla tua app. |
|
||||
|
||||
### Rilevamento delle entità
|
||||
## Gestione dei remoti
|
||||
|
||||
L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define<Entity>({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`:
|
||||
|
||||
| Funzione helper | Tipo di entità |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `defineObject` | Definizioni di oggetti personalizzati |
|
||||
| `defineLogicFunction` | Definizioni di funzioni logiche |
|
||||
| `definePreInstallLogicFunction` | Funzione logica di pre-installazione (viene eseguita prima dell'installazione) |
|
||||
| `definePostInstallLogicFunction` | Funzione logica di post-installazione (viene eseguita dopo l'installazione) |
|
||||
| `defineFrontComponent` | Definizioni dei componenti front-end |
|
||||
| `defineRole` | Definizioni di ruoli |
|
||||
| `defineField` | Estensioni di campo per oggetti esistenti |
|
||||
| `defineView` | Definizioni di viste salvate |
|
||||
| `defineNavigationMenuItem` | Definizioni delle voci del menu di navigazione |
|
||||
| `defineSkill` | Definizioni delle competenze degli agenti IA |
|
||||
|
||||
<Note>
|
||||
**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define<Entity>({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito.
|
||||
</Note>
|
||||
|
||||
Esempio di entità rilevata:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Comandi successivi aggiungeranno altri file e cartelle:
|
||||
|
||||
* `yarn twenty dev` genererà automaticamente due client API tipizzati in `node_modules/twenty-sdk/generated`: `CoreApiClient` (per i dati dell'area di lavoro tramite `/graphql`) e `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file tramite `/metadata`).
|
||||
* `yarn twenty entity:add` aggiungerà file di definizione delle entità sotto `src/` per i tuoi oggetti, funzioni, componenti front-end, ruoli, competenze e altro ancora.
|
||||
|
||||
## Autenticazione
|
||||
|
||||
La prima volta che esegui `yarn twenty auth:login`, ti verranno richiesti:
|
||||
|
||||
* URL dell'API (predefinito a http://localhost:3000 o al profilo dello spazio di lavoro corrente)
|
||||
* Chiave API
|
||||
|
||||
Le tue credenziali sono archiviate per utente in `~/.twenty/config.json`. Puoi mantenere più profili e passare da uno all'altro.
|
||||
|
||||
### Gestione delle aree di lavoro
|
||||
Un **remoto** è un server Twenty a cui la tua app si connette. Durante la configurazione, lo strumento di scaffolding ne crea uno automaticamente per te. Puoi aggiungere altri remoti o passare da uno all'altro in qualsiasi momento.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Una volta che hai cambiato area di lavoro con `yarn twenty auth:switch`, tutti i comandi successivi utilizzeranno quell'area di lavoro per impostazione predefinita. Puoi comunque sovrascriverla temporaneamente con `--workspace <name>`.
|
||||
Le tue credenziali sono archiviate in `~/.twenty/config.json`.
|
||||
|
||||
## Server di sviluppo locale (`yarn twenty server`)
|
||||
|
||||
La CLI può gestire un server Twenty locale in esecuzione in Docker. Questo è lo stesso server avviato automaticamente quando crei lo scheletro di un'app con `create-twenty-app`, ma puoi anche gestirlo manualmente.
|
||||
|
||||
### Avvio del server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
Questo scarica l'immagine Docker `twentycrm/twenty-app-dev:latest` (se non è già presente), crea un container chiamato `twenty-app-dev` e lo avvia sulla porta **2020**. La CLI attende che il server superi il controllo di integrità prima di restituire il controllo.
|
||||
|
||||
Vengono creati due volumi Docker per mantenere i dati tra i riavvii:
|
||||
|
||||
* `twenty-app-dev-data` — database PostgreSQL
|
||||
* `twenty-app-dev-storage` — archiviazione file
|
||||
|
||||
Se la porta 2020 è già in uso, puoi avviare su una porta diversa:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
La CLI configura automaticamente le variabili interne del container `NODE_PORT` e `SERVER_URL` per corrispondere alla porta scelta, in modo che funzioni logiche, OAuth e tutto il resto del networking interno funzionino correttamente.
|
||||
|
||||
Una volta avviato, il server viene registrato automaticamente come remoto `local` nella configurazione della CLI.
|
||||
|
||||
### Verifica dello stato del server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
Mostra se il server è in esecuzione, il suo URL e le credenziali di accesso predefinite (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### Visualizzazione dei log del server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
Trasmette in streaming i log del container. Usa `--lines` per controllare quante righe recenti mostrare:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### Arresto del server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
Arresta il container. I tuoi dati vengono conservati nei volumi Docker — il prossimo `start` riprenderà da dove avevi lasciato.
|
||||
|
||||
### Reimpostazione del server
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
Rimuove il container **e** elimina entrambi i volumi Docker, cancellando tutti i dati. Il prossimo `start` crea un'istanza nuova.
|
||||
|
||||
<Note>
|
||||
Il server richiede che **Docker** sia in esecuzione. Se vedi l'errore "Docker not running", assicurati che Docker Desktop (o il demone Docker) sia avviato.
|
||||
</Note>
|
||||
|
||||
### Riferimento ai comandi
|
||||
|
||||
| Comando | Descrizione |
|
||||
| -------------------------------------- | --------------------------------------------------------- |
|
||||
| `yarn twenty server start` | Avvia il server locale (scarica l'immagine se necessario) |
|
||||
| `yarn twenty server start --port 3030` | Avvia su una porta personalizzata |
|
||||
| `yarn twenty server stop` | Arresta il server (conserva i dati) |
|
||||
| `yarn twenty server status` | Mostra stato del server, URL e credenziali |
|
||||
| `yarn twenty server logs` | Trasmetti in streaming i log del server |
|
||||
| `yarn twenty server logs --lines 100` | Mostra le ultime 100 righe di log |
|
||||
| `yarn twenty server reset` | Elimina tutti i dati e riparti da zero |
|
||||
|
||||
## CI con GitHub Actions
|
||||
|
||||
Lo strumento di scaffolding genera un workflow GitHub Actions pronto all'uso in `.github/workflows/ci.yml`. Esegue automaticamente i test di integrazione a ogni push su `main` e sulle pull request.
|
||||
|
||||
Il workflow:
|
||||
|
||||
1. Esegue il checkout del tuo codice
|
||||
2. Avvia un server Twenty temporaneo utilizzando l'azione `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
|
||||
3. Installa le dipendenze con `yarn install --immutable`
|
||||
4. Esegue `yarn test` con `TWENTY_API_URL` e `TWENTY_API_KEY` iniettati dagli output dell'azione
|
||||
|
||||
```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 }}
|
||||
```
|
||||
|
||||
Non è necessario configurare alcun secret — l'azione `spawn-twenty-docker-image` avvia un server Twenty effimero direttamente nel runner e fornisce i dettagli di connessione. Il secret `GITHUB_TOKEN` è fornito automaticamente da GitHub.
|
||||
|
||||
Per fissare una versione specifica di Twenty invece di `latest`, modifica la variabile d'ambiente `TWENTY_VERSION` all'inizio del workflow.
|
||||
|
||||
## Configurazione manuale (senza lo scaffolder)
|
||||
|
||||
Sebbene consigliamo di utilizzare `create-twenty-app` per la migliore esperienza iniziale, puoi anche configurare un progetto manualmente. Non installare la CLI globalmente. Invece, aggiungi `twenty-sdk` come dipendenza locale e collega un unico script nel tuo package.json:
|
||||
Se preferisci configurare tutto manualmente invece di usare `create-twenty-app`, puoi farlo in due passaggi.
|
||||
|
||||
**1. Aggiungi `twenty-sdk` e `twenty-client-sdk` come dipendenze:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Quindi aggiungi uno script `twenty`:
|
||||
**2. Aggiungi uno script `twenty` al tuo `package.json`:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -221,25 +393,19 @@ Quindi aggiungi uno script `twenty`:
|
||||
}
|
||||
```
|
||||
|
||||
Ora puoi eseguire tutti i comandi tramite `yarn twenty <command>`, ad es. `yarn twenty dev`, `yarn twenty help`, ecc.
|
||||
Ora puoi eseguire `yarn twenty dev`, `yarn twenty help` e tutti gli altri comandi.
|
||||
|
||||
## Come utilizzare un'istanza locale di Twenty
|
||||
|
||||
Se stai già eseguendo un'istanza di Twenty in locale (ad es. tramite `npx nx start twenty-server`), puoi connetterti ad essa invece di usare Docker:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
Non installare `twenty-sdk` globalmente. Usalo sempre come dipendenza locale del progetto, in modo che ogni progetto possa fissare la propria versione.
|
||||
</Note>
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
* Errori di autenticazione: esegui `yarn twenty auth:login` e assicurati che la tua chiave API abbia i permessi richiesti.
|
||||
* Impossibile connettersi al server: verifica l'URL dell'API e che il server Twenty sia raggiungibile.
|
||||
* Tipi o client mancanti/obsoleti: riavvia `yarn twenty dev` — genera automaticamente il client tipizzato.
|
||||
* Modalità di sviluppo non sincronizzata: assicurati che `yarn twenty dev` sia in esecuzione e che le modifiche non vengano ignorate dal tuo ambiente.
|
||||
Se riscontri problemi:
|
||||
|
||||
Canale di supporto su Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
* Assicurati che **Docker sia in esecuzione** prima di avviare lo strumento di scaffolding con un'istanza locale.
|
||||
* Assicurati di usare **Node.js 24+** (`node -v` per verificare).
|
||||
* Assicurati che **Corepack sia abilitato** (`corepack enable`) in modo che Yarn 4 sia disponibile.
|
||||
* Prova a eliminare `node_modules` ed eseguire di nuovo `yarn install` se le dipendenze sembrano danneggiate.
|
||||
|
||||
Ancora bloccato? Chiedi aiuto su [Discord di Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
|
||||
|
||||
@@ -4,15 +4,75 @@ description: Distribuisci la tua app Twenty nel marketplace oppure distribuiscil
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.
|
||||
Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione.
|
||||
</Warning>
|
||||
|
||||
## Panoramica
|
||||
|
||||
Una volta che la tua app è stata [compilata e testata localmente](/l/it/developers/extend/apps/building), hai due modalità per distribuirla:
|
||||
|
||||
* **Distribuisci un tarball** — carica la tua app direttamente su un server Twenty specifico per uso interno o privato.
|
||||
* **Pubblica su npm** — elenca la tua app nel marketplace di Twenty affinché qualsiasi spazio di lavoro possa scoprirla e installarla.
|
||||
* **Esegui il push di un tarball** — distribuisci la tua app su un server Twenty specifico per uso interno senza renderla pubblicamente disponibile.
|
||||
|
||||
Entrambi i percorsi partono dalla stessa fase di **build**.
|
||||
|
||||
## Compilazione della tua app
|
||||
|
||||
Esegui il comando di build per compilare la tua app e generare un `manifest.json` pronto per la distribuzione:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
Questo compila i sorgenti TypeScript, transpila le funzioni di logica e i componenti front-end e scrive tutto in `.twenty/output/`. Aggiungi `--tarball` per produrre anche un pacchetto `.tgz` per la distribuzione manuale o il comando di deploy.
|
||||
|
||||
## Distribuzione su un server (tarball)
|
||||
|
||||
Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo aziendali o build sperimentali — puoi distribuire un tarball direttamente su un server Twenty.
|
||||
|
||||
### Prerequisiti
|
||||
|
||||
Prima della distribuzione, ti serve un remote configurato che punti al server di destinazione. I remote memorizzano localmente l'URL del server e le credenziali di autenticazione in `~/.twenty/config.json`.
|
||||
|
||||
Aggiungi un remote:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Distribuzione
|
||||
|
||||
Compila e carica la tua app sul server in un solo passaggio:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Condivisione di un'app distribuita
|
||||
|
||||
Le app in formato tarball non sono elencate nel marketplace pubblico, quindi altri spazi di lavoro sullo stesso server non le troveranno navigando. Per condividere un'app distribuita:
|
||||
|
||||
1. Vai su **Impostazioni > Applicazioni > Registrazioni** e apri la tua app
|
||||
2. Nella scheda **Distribuzione**, fai clic su **Copia link di condivisione**
|
||||
3. Condividi questo link con utenti su altri spazi di lavoro — li porterà direttamente alla pagina di installazione dell'app
|
||||
|
||||
Il link di condivisione utilizza l'URL di base del server (senza alcun sottodominio dello spazio di lavoro) così funziona per qualsiasi spazio di lavoro sul server.
|
||||
|
||||
<Warning>
|
||||
La condivisione delle app private è una funzionalità Enterprise. Vai a [Impostazioni > Pannello di amministrazione > Enterprise](/settings/admin-panel#enterprise) per abilitarla.
|
||||
</Warning>
|
||||
|
||||
### Gestione delle versioni
|
||||
|
||||
Per rilasciare un aggiornamento:
|
||||
|
||||
1. Incrementa il campo `version` nel tuo `package.json`
|
||||
2. Esegui `yarn twenty deploy` (oppure `yarn twenty deploy --remote production`)
|
||||
3. Gli spazi di lavoro che hanno l'app installata vedranno l'aggiornamento disponibile nelle proprie impostazioni
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## Pubblicazione su npm
|
||||
|
||||
@@ -21,29 +81,69 @@ La pubblicazione su npm rende la tua app scopribile nel marketplace di Twenty. Q
|
||||
### Requisiti
|
||||
|
||||
* Un account [npm](https://www.npmjs.com)
|
||||
* Il nome del tuo pacchetto **deve** usare il prefisso `twenty-app-` (es. `twenty-app-postcard-sender`)
|
||||
* La parola chiave `twenty-app` nell'array `keywords` del tuo `package.json` (già inclusa quando inizializzi con `create-twenty-app`)
|
||||
|
||||
### Passaggi
|
||||
|
||||
1. **Compila la tua app** — la CLI compila i tuoi sorgenti TypeScript e genera il manifest dell'applicazione:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Pubblica su npm** — esegui il push del pacchetto compilato al registro npm:
|
||||
### Metadati del marketplace
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
La configurazione `defineApplication()` supporta campi opzionali che controllano come la tua app appare nel marketplace. Usa `logoUrl` e `screenshots` per fare riferimento alle immagini nella cartella `public/`:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Rilevamento automatico
|
||||
Vedi l'[accordion defineApplication](/l/it/developers/extend/apps/building#defineentity-functions) nella pagina Building Apps per l'elenco completo dei campi del marketplace (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, ecc.).
|
||||
|
||||
I pacchetti con il prefisso `twenty-app-` vengono rilevati automaticamente dal catalogo del marketplace di Twenty. Una volta pubblicata, la tua app compare nel marketplace entro pochi minuti — non è richiesta alcuna registrazione o approvazione manuale.
|
||||
### Pubblica
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
Per pubblicare con un dist-tag specifico (ad es. `beta` o `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### Come funziona l'individuazione nel marketplace
|
||||
|
||||
Il server Twenty sincronizza il proprio catalogo del marketplace dal registro npm **ogni ora**.
|
||||
|
||||
Puoi attivare la sincronizzazione immediatamente invece di aspettare:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
I metadati visualizzati nel marketplace provengono dalla configurazione `defineApplication()` — campi come `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` e `termsUrl`.
|
||||
|
||||
<Note>
|
||||
Se la tua app non definisce un `aboutDescription` in `defineApplication()`, il marketplace userà automaticamente il `README.md` del tuo pacchetto su npm come contenuto della pagina Informazioni. Questo significa che puoi mantenere un unico README sia per npm sia per il marketplace di Twenty. Se desideri una descrizione diversa nel marketplace, imposta esplicitamente `aboutDescription`.
|
||||
</Note>
|
||||
|
||||
### Pubblicazione con CI
|
||||
|
||||
Il progetto generato include un workflow di GitHub Actions che pubblica a ogni release. Esegue `app:build`, quindi `npm publish --provenance` dall'output di build:
|
||||
Usa questo workflow di GitHub Actions per pubblicare automaticamente a ogni release (usa [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
Per altri sistemi CI (GitLab CI, CircleCI, ecc.), si applicano gli stessi tre comandi: `yarn install`, `npx twenty build`, quindi `npm publish` da `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**npm provenance** è opzionale ma consigliata. La pubblicazione con `--provenance` aggiunge un badge di attendibilità alla tua scheda npm, consentendo agli utenti di verificare che il pacchetto sia stato creato a partire da uno specifico commit in una pipeline CI pubblica. Consulta la [documentazione su npm provenance](https://docs.npmjs.com/generating-provenance-statements) per le istruzioni di configurazione.
|
||||
</Tip>
|
||||
|
||||
## Distribuzione interna
|
||||
|
||||
Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo per l'azienda o build sperimentali — puoi eseguire il push di un tarball direttamente su un server Twenty.
|
||||
|
||||
### Push di un tarball
|
||||
|
||||
Compila la tua app e distribuiscila su un server specifico in un solo passaggio:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
Qualsiasi spazio di lavoro su quel server può quindi installare e aggiornare l'app dalla pagina delle impostazioni **Applicazioni**.
|
||||
|
||||
### Gestione delle versioni
|
||||
|
||||
Per rilasciare un aggiornamento:
|
||||
|
||||
1. Incrementa il campo `version` nel tuo `package.json`
|
||||
2. Esegui il push di un nuovo tarball con `npx twenty publish --server <server-url>`
|
||||
3. Gli spazi di lavoro su quel server vedranno l'aggiornamento disponibile nelle proprie impostazioni
|
||||
Per altri sistemi CI (GitLab CI, CircleCI, ecc.), si applicano gli stessi tre comandi: `yarn install`, `yarn twenty build`, quindi `npm publish` da `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
Le app interne sono limitate al server su cui vengono caricate. Non compariranno nel marketplace pubblico e non potranno essere installate dagli spazi di lavoro su altri server.
|
||||
**npm provenance** è opzionale ma consigliata. La pubblicazione con `--provenance` aggiunge un badge di attendibilità alla tua scheda npm, consentendo agli utenti di verificare che il pacchetto sia stato creato a partire da uno specifico commit in una pipeline CI pubblica. Consulta la [documentazione su npm provenance](https://docs.npmjs.com/generating-provenance-statements) per le istruzioni di configurazione.
|
||||
</Note>
|
||||
|
||||
## Categorie di app
|
||||
## Installazione delle app
|
||||
|
||||
Twenty organizza le app in tre categorie in base a come vengono distribuite:
|
||||
Una volta che un'app è stata pubblicata (npm) o distribuita (tarball), gli spazi di lavoro possono installarla tramite l'interfaccia utente.
|
||||
|
||||
| Categoria | Come funziona | Visibile nel marketplace? |
|
||||
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
||||
| **Sviluppo** | App in modalità di sviluppo locale eseguite tramite `yarn twenty dev`. Usate per la compilazione e i test. | No |
|
||||
| **Pubblicata** | App pubblicate su npm con il prefisso `twenty-app-`. Elencate nel marketplace per l'installazione da parte di qualsiasi spazio di lavoro. | Sì |
|
||||
| **Interna** | App distribuite tramite tarball su un server specifico. Disponibili solo per gli spazi di lavoro su quel server. | No |
|
||||
Vai alla pagina **Impostazioni > Applicazioni** in Twenty, dove è possibile sfogliare e installare sia le app del marketplace sia quelle distribuite tramite tarball.
|
||||
|
||||
<Tip>
|
||||
Inizia in modalità **Sviluppo** mentre crei la tua app. Quando è pronta, scegli **Pubblicata** (npm) per un'ampia distribuzione oppure **Interna** (tarball) per una distribuzione privata.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
Puoi anche installare le app dalla riga di comando:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
@@ -55,11 +55,12 @@ export const main = async (
|
||||
params: { companyId: string },
|
||||
) => {
|
||||
const { companyId } = params;
|
||||
|
||||
// Replace with your Twenty GraphQL endpoint
|
||||
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
|
||||
// Cloud: https://api.twenty.com/graphql
|
||||
// Self-hosted: https://your-domain.com/graphql
|
||||
const graphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
|
||||
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
// Replace with your API key from Settings → APIs
|
||||
const authToken = 'YOUR_API_KEY';
|
||||
@@ -79,11 +80,40 @@ export const main = async (
|
||||
const pdfBlob = await pdfResponse.blob();
|
||||
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
|
||||
|
||||
// Step 2: Upload the file via GraphQL multipart upload
|
||||
const fieldMetadataIdQuery = `
|
||||
query FindUploadFileFieldMetadataId {
|
||||
objects {
|
||||
edges {
|
||||
node {
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
|
||||
const response = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
query: fieldMetadataIdQuery,
|
||||
}
|
||||
});
|
||||
const result = await response.json();
|
||||
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
|
||||
|
||||
// Step 3: Upload the file via GraphQL multipart upload
|
||||
const uploadMutation = `
|
||||
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
|
||||
uploadFile(file: $file, fileFolder: $fileFolder) {
|
||||
path
|
||||
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
|
||||
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -91,12 +121,12 @@ export const main = async (
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.append('operations', JSON.stringify({
|
||||
query: uploadMutation,
|
||||
variables: { file: null, fileFolder: 'Attachment' },
|
||||
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
|
||||
}));
|
||||
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
|
||||
uploadForm.append('0', pdfFile);
|
||||
|
||||
const uploadResponse = await fetch(graphqlEndpoint, {
|
||||
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
body: uploadForm,
|
||||
@@ -108,15 +138,15 @@ export const main = async (
|
||||
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
|
||||
}
|
||||
|
||||
const filePath = uploadResult.data?.uploadFile?.path;
|
||||
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('No file path returned from upload');
|
||||
if (!fileId) {
|
||||
throw new Error('No file id returned from upload');
|
||||
}
|
||||
|
||||
// Step 3: Create the attachment linked to the company
|
||||
// Step 4: Create the attachment linked to the company
|
||||
const attachmentMutation = `
|
||||
mutation CreateAttachment($data: AttachmentCreateInput!) {
|
||||
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
|
||||
createAttachment(data: $data) {
|
||||
id
|
||||
name
|
||||
@@ -124,7 +154,7 @@ export const main = async (
|
||||
}
|
||||
`;
|
||||
|
||||
const attachmentResponse = await fetch(graphqlEndpoint, {
|
||||
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -135,8 +165,13 @@ export const main = async (
|
||||
variables: {
|
||||
data: {
|
||||
name: filename,
|
||||
fullPath: filePath,
|
||||
companyId,
|
||||
targetCompanyId: companyId,
|
||||
file: [
|
||||
{
|
||||
fileId: fileId,
|
||||
label: filename
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -156,14 +191,14 @@ export const main = async (
|
||||
|
||||
#### Per allegare a un oggetto diverso
|
||||
|
||||
Sostituisci `companyId` con il campo appropriato:
|
||||
Sostituisci `targetCompanyId` con il campo appropriato:
|
||||
|
||||
| Oggetto | Nome del campo |
|
||||
| ---------------------- | -------------------- |
|
||||
| Azienda | `companyId` |
|
||||
| Persona | `personId` |
|
||||
| Opportunità | `opportunityId` |
|
||||
| Oggetto personalizzato | `yourCustomObjectId` |
|
||||
| Oggetto | Nome del campo |
|
||||
| ---------------------- | -------------------------- |
|
||||
| Azienda | `targetCompanyId` |
|
||||
| Persona | `targetPersonId` |
|
||||
| Opportunità | `targetOpportunityId` |
|
||||
| Oggetto personalizzato | `targetYourCustomObjectId` |
|
||||
|
||||
Aggiorna sia il parametro della funzione sia l'oggetto `variables.data` nella mutation dell'allegato.
|
||||
|
||||
|
||||
@@ -9,79 +9,137 @@ Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ain
|
||||
|
||||
Os apps permitem que você estenda o Twenty com objetos, campos, funções de lógica, habilidades de IA e componentes de UI personalizados — tudo gerenciado como código.
|
||||
|
||||
**O que você pode fazer hoje:**
|
||||
|
||||
* Defina objetos e campos personalizados como código (modelo de dados gerenciado)
|
||||
* Crie funções de lógica com gatilhos personalizados (rotas HTTP, cron, eventos de banco de dados)
|
||||
* Defina habilidades para agentes de IA
|
||||
* Crie componentes de front-end que renderizam dentro da UI do Twenty
|
||||
* Implemente o mesmo aplicativo em vários espaços de trabalho
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
* Node.js 24+ e Yarn 4
|
||||
* Docker (para o servidor de desenvolvimento local do Twenty)
|
||||
Antes de começar, verifique se o seguinte está instalado na sua máquina:
|
||||
|
||||
## Primeiros passos
|
||||
* **Node.js 24+** — [Baixe aqui](https://nodejs.org/)
|
||||
* **Yarn 4** — Vem com o Node.js via Corepack. Ative-o executando `corepack enable`
|
||||
* **Docker** — [Baixe aqui](https://www.docker.com/products/docker-desktop/). Necessário para executar uma instância local do Twenty. Não é necessário se você já tiver um servidor Twenty em execução.
|
||||
|
||||
Crie um novo aplicativo usando o gerador oficial, depois autentique-se e comece a desenvolver:
|
||||
## Passo 1: Gere o scaffold do seu aplicativo
|
||||
|
||||
Abra um terminal e execute:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
Será solicitado que você informe um nome e uma descrição para o seu aplicativo. Pressione **Enter** para aceitar os valores padrão.
|
||||
|
||||
Isso cria uma nova pasta chamada `my-twenty-app` com tudo de que você precisa.
|
||||
|
||||
<Note>
|
||||
O gerador de scaffold oferece suporte a estas flags:
|
||||
|
||||
* `--minimal` — gera apenas os arquivos essenciais, sem exemplos (padrão)
|
||||
* `--exhaustive` — gera todas as entidades de exemplo
|
||||
* `--name <name>` — define o nome do aplicativo (pula o prompt)
|
||||
* `--display-name <displayName>` — define o nome de exibição (pula o prompt)
|
||||
* `--description <description>` — define a descrição (pula o prompt)
|
||||
* `--skip-local-instance` — ignora o prompt de configuração do servidor local
|
||||
</Note>
|
||||
|
||||
## Passo 2: Configure uma instância local do Twenty
|
||||
|
||||
O gerador de scaffold perguntará:
|
||||
|
||||
> **Você gostaria de configurar uma instância local do Twenty?**
|
||||
|
||||
* **Digite `yes`** (recomendado) — Isso baixa a imagem Docker `twenty-app-dev` e inicia um servidor Twenty local na porta `2020`. Certifique-se de que o Docker esteja em execução antes de continuar.
|
||||
* **Digite `no`** — Escolha esta opção se você já tiver um servidor Twenty em execução localmente.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Deve iniciar instância local?" />
|
||||
</div>
|
||||
|
||||
## Passo 3: Faça login no seu espaço de trabalho
|
||||
|
||||
Em seguida, uma janela do navegador será aberta com a página de login do Twenty. Faça login com a conta de demonstração pré-configurada:
|
||||
|
||||
* **E-mail:** `tim@apple.dev`
|
||||
* **Senha:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Tela de login do Twenty" />
|
||||
</div>
|
||||
|
||||
## Passo 4: Autorize o aplicativo
|
||||
|
||||
Após fazer login, você verá uma tela de autorização. Isso permite que seu aplicativo interaja com seu espaço de trabalho.
|
||||
|
||||
Clique em **Authorize** para continuar.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Tela de autorização da CLI do Twenty" />
|
||||
</div>
|
||||
|
||||
Depois de autorizado, seu terminal confirmará que tudo está configurado.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Scaffold do aplicativo criado com sucesso" />
|
||||
</div>
|
||||
|
||||
## Passo 5: Comece a desenvolver
|
||||
|
||||
Entre na nova pasta do seu aplicativo e inicie o servidor de desenvolvimento:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
O gerador de estrutura oferece suporte a dois modos para controlar quais arquivos de exemplo são incluídos:
|
||||
Isso observa seus arquivos-fonte, recompila a cada alteração e sincroniza seu aplicativo com o servidor Twenty local automaticamente. Você deverá ver um painel de status em tempo real no seu terminal.
|
||||
|
||||
Para uma saída mais detalhada (logs de build, solicitações de sincronização, rastros de erro), use a flag `--verbose`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
A partir daqui você pode:
|
||||
<Warning>
|
||||
O modo de desenvolvimento só está disponível em instâncias do Twenty em modo de desenvolvimento (`NODE_ENV=development`). Instâncias de produção rejeitam solicitações de sincronização de desenvolvimento. Use `yarn twenty deploy` para fazer o deploy em servidores de produção — veja [Publicando aplicativos](/l/pt/developers/extend/apps/publishing) para detalhes.
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Saída do terminal no modo de desenvolvimento" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## Passo 6: Veja seu aplicativo no Twenty
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
Abra [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) no seu navegador. Navegue até **Settings > Apps** e selecione a aba **Developer**. Você deverá ver seu aplicativo listado em **Your Apps**:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Lista Your Apps exibindo My twenty app" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
Clique em **My twenty app** para abrir o seu **registro do aplicativo**. Um registro é um registro em nível de servidor que descreve seu aplicativo — seu nome, identificador exclusivo, credenciais OAuth e origem (local, npm ou tarball). Ele reside no servidor, não dentro de nenhum espaço de trabalho específico. Quando você instala um aplicativo em um espaço de trabalho, o Twenty cria uma **aplicação** com escopo do espaço de trabalho que aponta para esse registro. Um registro pode ser instalado em vários espaços de trabalho no mesmo servidor.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Detalhes do registro do aplicativo" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
Clique em **View installed app** para ver o aplicativo instalado. A aba **About** mostra a versão atual e as opções de gerenciamento:
|
||||
|
||||
Veja também: as páginas de referência da CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Aplicativo instalado — aba About" />
|
||||
</div>
|
||||
|
||||
## Estrutura do projeto (com scaffold)
|
||||
Altere para a aba **Content** para ver tudo o que seu aplicativo oferece — objetos, campos, funções de lógica e agentes:
|
||||
|
||||
Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Aplicativo instalado — aba Content" />
|
||||
</div>
|
||||
|
||||
* Copia um aplicativo base mínimo para `my-twenty-app/`
|
||||
* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4
|
||||
* Cria arquivos de configuração e scripts conectados à CLI `twenty`
|
||||
* Gera arquivos principais (configuração da aplicação, papel padrão para funções de lógica, funções de pré-instalação e pós-instalação) além de arquivos de exemplo com base no modo de geração de estrutura
|
||||
Tudo pronto! Edite qualquer arquivo em `src/` e as alterações serão detectadas automaticamente.
|
||||
|
||||
Um app recém-criado com o modo padrão `--exhaustive` fica assim:
|
||||
Acesse [Criando aplicativos](/l/pt/developers/extend/apps/building) para um guia detalhado sobre criação de objetos, funções de lógica, componentes de front-end, habilidades e mais.
|
||||
|
||||
---
|
||||
|
||||
## Estrutura do projeto
|
||||
|
||||
O gerador de scaffold cria a seguinte estrutura de arquivos (mostrada com o modo `--exhaustive`, que inclui exemplos para cada tipo de entidade):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -94,124 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
Com `--minimal`, apenas os arquivos principais são criados (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
|
||||
Por padrão (`--minimal`), apenas os arquivos principais são criados: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`. Use `--exhaustive` para incluir todos os arquivos de exemplo mostrados acima.
|
||||
|
||||
Em alto nível:
|
||||
### Arquivos principais
|
||||
|
||||
* **package.json**: Declara o nome do app, versão, engines (Node 24+, Yarn 4), e adiciona `twenty-sdk` além de um script `twenty` que delega para a CLI `twenty` local. Execute `yarn twenty help` para listar todos os comandos disponíveis.
|
||||
* **.gitignore**: Ignora artefatos comuns como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, pastas de cobertura, arquivos de log e arquivos `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloqueiam e configuram a ferramenta Yarn 4 usada pelo projeto.
|
||||
* **.nvmrc**: Fixa a versão do Node.js esperada pelo projeto.
|
||||
* **.oxlintrc.json** e **tsconfig.json**: Fornecem lint e configuração do TypeScript para os fontes TypeScript do seu aplicativo.
|
||||
* **README.md**: Um README curto na raiz do aplicativo com instruções básicas.
|
||||
* **public/**: Uma pasta para armazenar recursos públicos (imagens, fontes, arquivos estáticos) que serão servidos com sua aplicação. Os arquivos colocados aqui são enviados durante a sincronização e ficam acessíveis em tempo de execução.
|
||||
* **src/**: O local principal onde você define seu aplicativo como código
|
||||
| Arquivo / Pasta | Finalidade |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `package.json` | Declara o nome, a versão e as dependências do seu aplicativo. Inclui um script `twenty` para que você possa executar `yarn twenty help` e ver todos os comandos. |
|
||||
| `src/application-config.ts` | **Obrigatório.** O principal arquivo de configuração do seu aplicativo. |
|
||||
| `src/roles/` | Define papéis que controlam o que suas funções de lógica podem acessar. |
|
||||
| `src/logic-functions/` | Funções do lado do servidor acionadas por rotas, agendamentos do cron ou eventos de banco de dados. |
|
||||
| `src/front-components/` | Componentes React que renderizam dentro da interface do Twenty. |
|
||||
| `src/objects/` | Definições de objetos personalizados para estender seu modelo de dados. |
|
||||
| `src/fields/` | Campos personalizados adicionados a objetos existentes. |
|
||||
| `src/views/` | Configurações de visualizações salvas. |
|
||||
| `src/navigation-menu-items/` | Links personalizados na navegação da barra lateral. |
|
||||
| `src/skills/` | Habilidades que estendem os agentes de IA do Twenty. |
|
||||
| `src/agents/` | Agentes de IA com prompts personalizados. |
|
||||
| `src/page-layouts/` | Layouts de página personalizados para visualizações de registros. |
|
||||
| `src/__tests__/` | Testes de integração (configuração + teste de exemplo). |
|
||||
| `public/` | Recursos estáticos (imagens, fontes) servidos com seu aplicativo. |
|
||||
|
||||
### Detecção de entidades
|
||||
## Gerenciando remotos
|
||||
|
||||
O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define<Entity>({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`:
|
||||
|
||||
| Função utilitária | Tipo de entidade |
|
||||
| -------------------------------- | -------------------------------------------------------------------- |
|
||||
| `defineObject` | Definições de objetos personalizados |
|
||||
| `defineLogicFunction` | Definições de funções de lógica |
|
||||
| `definePreInstallLogicFunction` | Função de lógica de pré-instalação (é executada antes da instalação) |
|
||||
| `definePostInstallLogicFunction` | Função de lógica de pós-instalação (é executada após a instalação) |
|
||||
| `defineFrontComponent` | Definições de componentes de front-end |
|
||||
| `defineRole` | Definições de papéis |
|
||||
| `defineField` | Extensões de campos para objetos existentes |
|
||||
| `defineView` | Definições de visualizações salvas |
|
||||
| `defineNavigationMenuItem` | Definições de itens do menu de navegação |
|
||||
| `defineSkill` | Definições de habilidades de agente de IA |
|
||||
|
||||
<Note>
|
||||
**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define<Entity>({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito.
|
||||
</Note>
|
||||
|
||||
Exemplo de uma entidade detectada:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Comandos posteriores adicionarão mais arquivos e pastas:
|
||||
|
||||
* `yarn twenty dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end, papéis e habilidades personalizados, entre outros.
|
||||
|
||||
## Autenticação
|
||||
|
||||
Na primeira vez que você executar `yarn twenty auth:login`, será solicitado o seguinte:
|
||||
|
||||
* URL da API (padrão: http://localhost:3000 ou o perfil do seu espaço de trabalho atual)
|
||||
* Chave de API
|
||||
|
||||
Suas credenciais são armazenadas por usuário em `~/.twenty/config.json`. Você pode manter vários perfis e alternar entre eles.
|
||||
|
||||
### Gerenciando espaços de trabalho
|
||||
Um **remoto** é um servidor Twenty ao qual seu aplicativo se conecta. Durante a configuração, o gerador de scaffold cria um para você automaticamente. Você pode adicionar mais remotos ou alternar entre eles a qualquer momento.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Depois que você alternar os espaços de trabalho com `yarn twenty auth:switch`, todos os comandos subsequentes usarão esse espaço de trabalho por padrão. Você ainda pode substituí-lo temporariamente com `--workspace <name>`.
|
||||
Suas credenciais são armazenadas em `~/.twenty/config.json`.
|
||||
|
||||
## Servidor de desenvolvimento local (`yarn twenty server`)
|
||||
|
||||
A CLI pode gerenciar um servidor Twenty local em execução no Docker. Este é o mesmo servidor iniciado automaticamente quando você cria o scaffold de um aplicativo com `create-twenty-app`, mas você também pode gerenciá-lo manualmente.
|
||||
|
||||
### Iniciando o servidor
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
Isso baixa a imagem Docker `twentycrm/twenty-app-dev:latest` (se ainda não estiver presente), cria um contêiner chamado `twenty-app-dev` e o inicia na porta **2020**. A CLI aguarda até que o servidor passe na verificação de integridade antes de retornar.
|
||||
|
||||
Dois volumes do Docker são criados para persistir os dados entre reinicializações:
|
||||
|
||||
* `twenty-app-dev-data` — banco de dados PostgreSQL
|
||||
* `twenty-app-dev-storage` — armazenamento de arquivos
|
||||
|
||||
Se a porta 2020 já estiver em uso, você pode iniciar em uma porta diferente:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
A CLI configura automaticamente as variáveis internas do contêiner `NODE_PORT` e `SERVER_URL` para corresponderem à porta escolhida, para que as funções de lógica, o OAuth e toda a comunicação interna de rede funcionem corretamente.
|
||||
|
||||
Depois de iniciado, o servidor é registrado automaticamente como o remoto `local` na configuração da sua CLI.
|
||||
|
||||
### Verificando o status do servidor
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
Exibe se o servidor está em execução, sua URL e as credenciais de login padrão (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### Visualizando os logs do servidor
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
Transmite os logs do contêiner. Use `--lines` para controlar quantas linhas recentes mostrar:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### Parando o servidor
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
Interrompe o contêiner. Seus dados são preservados nos volumes do Docker — o próximo `start` continua de onde você parou.
|
||||
|
||||
### Redefinindo o servidor
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
Remove o contêiner **e** exclui os dois volumes do Docker, apagando todos os dados. O próximo `start` cria uma instância nova.
|
||||
|
||||
<Note>
|
||||
O servidor requer que o **Docker** esteja em execução. Se você vir um erro "Docker not running", certifique-se de que o Docker Desktop (ou o daemon do Docker) esteja iniciado.
|
||||
</Note>
|
||||
|
||||
### Referência de comandos
|
||||
|
||||
| Comando | Descrição |
|
||||
| -------------------------------------- | ------------------------------------------------------ |
|
||||
| `yarn twenty server start` | Inicia o servidor local (baixa a imagem se necessário) |
|
||||
| `yarn twenty server start --port 3030` | Iniciar em uma porta personalizada |
|
||||
| `yarn twenty server stop` | Interrompe o servidor (preserva os dados) |
|
||||
| `yarn twenty server status` | Mostra o status do servidor, a URL e as credenciais |
|
||||
| `yarn twenty server logs` | Transmite os logs do servidor |
|
||||
| `yarn twenty server logs --lines 100` | Mostra as últimas 100 linhas de log |
|
||||
| `yarn twenty server reset` | Exclui todos os dados e inicia do zero |
|
||||
|
||||
## CI com GitHub Actions
|
||||
|
||||
O gerador de scaffold cria um workflow do GitHub Actions pronto para uso em `.github/workflows/ci.yml`. Ele executa seus testes de integração automaticamente a cada push para `main` e em pull requests.
|
||||
|
||||
O workflow:
|
||||
|
||||
1. Faz checkout do seu código
|
||||
2. Inicializa um servidor Twenty temporário usando a ação `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
|
||||
3. Instala as dependências com `yarn install --immutable`
|
||||
4. Executa `yarn test` com `TWENTY_API_URL` e `TWENTY_API_KEY` injetados a partir das saídas da ação
|
||||
|
||||
```yaml .github/workflows/ci.yml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run integration tests
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
```
|
||||
|
||||
Você não precisa configurar nenhum segredo — a ação `spawn-twenty-docker-image` inicia um servidor Twenty efêmero diretamente no runner e fornece os detalhes de conexão. O segredo `GITHUB_TOKEN` é fornecido automaticamente pelo GitHub.
|
||||
|
||||
Para fixar uma versão específica do Twenty em vez de `latest`, altere a variável de ambiente `TWENTY_VERSION` no topo do workflow.
|
||||
|
||||
## Configuração manual (sem o gerador)
|
||||
|
||||
Embora recomendemos usar `create-twenty-app` para a melhor experiência inicial, você também pode configurar um projeto manualmente. Não instale a CLI globalmente. Em vez disso, adicione `twenty-sdk` como uma dependência local e configure um único script no seu package.json:
|
||||
Se preferir configurar tudo por conta própria em vez de usar `create-twenty-app`, você pode fazer isso em duas etapas.
|
||||
|
||||
**1. Adicione `twenty-sdk` e `twenty-client-sdk` como dependências:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Em seguida, adicione um script `twenty`:
|
||||
**2. Adicione um script `twenty` ao seu `package.json`:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -221,25 +393,19 @@ Em seguida, adicione um script `twenty`:
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty dev`, `yarn twenty help`, etc.
|
||||
Agora você pode executar `yarn twenty dev`, `yarn twenty help` e todos os outros comandos.
|
||||
|
||||
## Como usar uma instância local do Twenty
|
||||
|
||||
Se você já estiver executando uma instância do Twenty localmente (por exemplo, via `npx nx start twenty-server`), você pode conectar-se a ela em vez de usar o Docker:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
Não instale o `twenty-sdk` globalmente. Use-o sempre como uma dependência local do projeto para que cada projeto possa fixar sua própria versão.
|
||||
</Note>
|
||||
|
||||
## Resolução de Problemas
|
||||
|
||||
* Erros de autenticação: execute `yarn twenty auth:login` e certifique-se de que sua chave de API tenha as permissões necessárias.
|
||||
* Não é possível conectar ao servidor: verifique a URL da API e se o servidor do Twenty está acessível.
|
||||
* Types or client missing/outdated: restart `yarn twenty dev` — it auto-generates the typed client.
|
||||
* Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment.
|
||||
Se você tiver problemas:
|
||||
|
||||
Canal de ajuda no Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
* Certifique-se de que o **Docker está em execução** antes de iniciar o scaffolder com uma instância local.
|
||||
* Certifique-se de que está usando **Node.js 24+** (`node -v` para verificar).
|
||||
* Certifique-se de que o **Corepack está ativado** (`corepack enable`) para que o Yarn 4 esteja disponível.
|
||||
* Tente excluir `node_modules` e executar `yarn install` novamente se as dependências parecerem corrompidas.
|
||||
|
||||
Ainda com dificuldades? Peça ajuda no [Discord da Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
|
||||
|
||||
@@ -4,15 +4,75 @@ description: Distribua seu aplicativo Twenty no Marketplace ou implante-o intern
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo.
|
||||
Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo.
|
||||
</Warning>
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Depois que seu aplicativo estiver [compilado e testado localmente](/l/pt/developers/extend/apps/building), você tem dois caminhos para distribuí-lo:
|
||||
|
||||
* **Implantar um tarball** — envie seu aplicativo diretamente para um servidor Twenty específico para uso interno ou privado.
|
||||
* **Publicar no npm** — liste seu aplicativo no Marketplace da Twenty para que qualquer espaço de trabalho possa descobrir e instalar.
|
||||
* **Enviar um tarball** — implante seu aplicativo em um servidor Twenty específico para uso interno sem torná-lo público.
|
||||
|
||||
Ambos os caminhos começam na mesma etapa de **build**.
|
||||
|
||||
## Compilando seu app
|
||||
|
||||
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
|
||||
|
||||
## Implantando em um servidor (tarball)
|
||||
|
||||
Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode implantar um tarball diretamente em um servidor Twenty.
|
||||
|
||||
### Pré-requisitos
|
||||
|
||||
Antes de implantar, você precisa de um remote configurado apontando para o servidor de destino. Os remotes armazenam a URL do servidor e as credenciais de autenticação localmente em `~/.twenty/config.json`.
|
||||
|
||||
Adicionar um remote:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Implantando
|
||||
|
||||
Compile e envie seu aplicativo para o servidor em uma única etapa:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Compartilhando um aplicativo implantado
|
||||
|
||||
Aplicativos em tarball não são listados no marketplace público, então outros espaços de trabalho no mesmo servidor não os descobrirão ao navegar. Para compartilhar um aplicativo implantado:
|
||||
|
||||
1. Vá para **Configurações > Aplicações > Registros** e abra seu aplicativo
|
||||
2. Na guia **Distribuição**, clique em **Copiar link de compartilhamento**
|
||||
3. Compartilhe esse link com usuários de outros espaços de trabalho — ele os leva diretamente para a página de instalação do aplicativo
|
||||
|
||||
O link de compartilhamento usa a URL base do servidor (sem qualquer subdomínio de espaço de trabalho), para funcionar em qualquer espaço de trabalho no servidor.
|
||||
|
||||
<Warning>
|
||||
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
|
||||
</Warning>
|
||||
|
||||
### Gerenciamento de versões
|
||||
|
||||
Para lançar uma atualização:
|
||||
|
||||
1. Atualize o campo `version` no seu `package.json`
|
||||
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
|
||||
3. Os espaços de trabalho que têm o aplicativo instalado verão a atualização disponível em suas configurações
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## Publicação no npm
|
||||
|
||||
@@ -21,29 +81,69 @@ Publicar no npm torna seu aplicativo descobrível no Marketplace da Twenty. Qual
|
||||
### Requisitos
|
||||
|
||||
* Uma conta no [npm](https://www.npmjs.com)
|
||||
* O nome do seu pacote **deve** usar o prefixo `twenty-app-` (por exemplo, `twenty-app-postcard-sender`)
|
||||
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
|
||||
|
||||
### Etapas
|
||||
|
||||
1. **Compile seu aplicativo** — a CLI compila seus códigos-fonte TypeScript e gera o manifesto do aplicativo:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Publicar no npm** — envie o pacote compilado para o registro do npm:
|
||||
### Metadados do Marketplace
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Descoberta automática
|
||||
See the [defineApplication accordion](/l/pt/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
Pacotes com o prefixo `twenty-app-` são detectados automaticamente pelo catálogo do Marketplace da Twenty. Depois de publicado, seu aplicativo aparece no Marketplace em poucos minutos — sem necessidade de registro manual ou aprovação.
|
||||
### Publish
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
Para publicar sob uma dist-tag específica (por exemplo, `beta` ou `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### Como funciona a descoberta no marketplace
|
||||
|
||||
O servidor Twenty sincroniza seu catálogo do marketplace a partir do registro do npm **a cada hora**.
|
||||
|
||||
You can trigger the sync immediately instead of waiting:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
|
||||
|
||||
<Note>
|
||||
Se o seu aplicativo não definir um `aboutDescription` em `defineApplication()`, o marketplace usará automaticamente o `README.md` do seu pacote no npm como conteúdo da página Sobre. Isso significa que você pode manter um único README tanto para o npm quanto para o marketplace da Twenty. Se quiser uma descrição diferente no marketplace, defina explicitamente `aboutDescription`.
|
||||
</Note>
|
||||
|
||||
### Publicação via CI
|
||||
|
||||
O projeto gerado inclui um workflow do GitHub Actions que publica a cada lançamento. Ele executa `app:build` e depois `npm publish --provenance` a partir da saída do build:
|
||||
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `npx twenty build` e depois `npm publish` a partir de `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**Proveniência do npm** é opcional, mas recomendada. Publicar com `--provenance` adiciona um selo de confiança à sua listagem no npm, permitindo que os usuários verifiquem que o pacote foi construído a partir de um commit específico em um pipeline de CI público. Consulte a [documentação de proveniência do npm](https://docs.npmjs.com/generating-provenance-statements) para instruções de configuração.
|
||||
</Tip>
|
||||
|
||||
## Distribuição interna
|
||||
|
||||
Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode enviar um tarball diretamente para um servidor Twenty.
|
||||
|
||||
### Enviar um tarball
|
||||
|
||||
Compile seu aplicativo e implante-o em um servidor específico em uma única etapa:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
Qualquer espaço de trabalho nesse servidor pode então instalar e atualizar o aplicativo na página de configurações de **Aplicativos**.
|
||||
|
||||
### Gerenciamento de versões
|
||||
|
||||
Para lançar uma atualização:
|
||||
|
||||
1. Atualize o campo `version` no seu `package.json`
|
||||
2. Envie um novo tarball com `npx twenty publish --server <server-url>`
|
||||
3. Os espaços de trabalho nesse servidor verão a atualização disponível nas suas configurações
|
||||
Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `yarn twenty build` e, em seguida, `npm publish` a partir de `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
Aplicativos internos ficam restritos ao servidor para o qual são enviados. Eles não aparecem no Marketplace público e não podem ser instalados por espaços de trabalho em outros servidores.
|
||||
**Proveniência do npm** é opcional, mas recomendada. Publicar com `--provenance` adiciona um selo de confiança à sua listagem no npm, permitindo que os usuários verifiquem que o pacote foi construído a partir de um commit específico em um pipeline de CI público. Consulte a [documentação de proveniência do npm](https://docs.npmjs.com/generating-provenance-statements) para instruções de configuração.
|
||||
</Note>
|
||||
|
||||
## Categorias de aplicativos
|
||||
## Instalando aplicativos
|
||||
|
||||
A Twenty organiza os aplicativos em três categorias com base em como são distribuídos:
|
||||
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
|
||||
|
||||
| Categoria | Como Funciona | Visível no Marketplace? |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
||||
| **Desenvolvimento** | Aplicativos em modo de desenvolvimento local executados via `yarn twenty dev`. Usados para compilação e testes. | Não |
|
||||
| **Publicado** | Aplicativos publicados no npm com o prefixo `twenty-app-`. Listados no Marketplace para que qualquer espaço de trabalho possa instalar. | Sim |
|
||||
| **Interno** | Aplicativos implantados via tarball em um servidor específico. Disponível apenas para espaços de trabalho nesse servidor. | Não |
|
||||
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
|
||||
|
||||
<Tip>
|
||||
Comece no modo de **Desenvolvimento** enquanto cria seu aplicativo. Quando estiver pronto, escolha **Publicado** (npm) para ampla distribuição ou **Interno** (tarball) para implantação privada.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
You can also install apps from the command line:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
@@ -297,6 +297,16 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Modo somente ambiente:** Se você definir `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, adicione estas variáveis ao seu arquivo `.env`.
|
||||
</Warning>
|
||||
|
||||
## Armazenamento S3
|
||||
|
||||
<Warning>
|
||||
Por padrão, o Twenty armazena os arquivos enviados no sistema de arquivos local. Para implantações em produção, use o S3 ou um serviço compatível com S3 (MinIO, DigitalOcean Spaces, etc.) para garantir que os arquivos persistam entre reinicializações do contêiner e possam escalar em várias instâncias de servidor.
|
||||
</Warning>
|
||||
|
||||
Defina `STORAGE_TYPE=S_3` e configure as variáveis `STORAGE_S3_*` pelo painel de administração ou `.env`. Veja a [referência de config-variables.ts](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts) para a lista completa de variáveis do S3.
|
||||
|
||||
Ao usar o S3 com recursos dependentes de CORS (por exemplo, downloads de arquivos no navegador), verifique se o seu bucket permite a origem do seu frontend do Twenty na sua configuração de CORS.
|
||||
|
||||
## Funções lógicas e interpretador de código
|
||||
|
||||
O Twenty oferece suporte a funções lógicas para fluxos de trabalho e ao interpretador de código para análise de dados com IA. Ambos executam código fornecido pelo usuário e exigem configuração explícita por motivos de segurança.
|
||||
|
||||
@@ -55,11 +55,12 @@ export const main = async (
|
||||
params: { companyId: string },
|
||||
) => {
|
||||
const { companyId } = params;
|
||||
|
||||
// Replace with your Twenty GraphQL endpoint
|
||||
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
|
||||
// Cloud: https://api.twenty.com/graphql
|
||||
// Self-hosted: https://your-domain.com/graphql
|
||||
const graphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
|
||||
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
// Replace with your API key from Settings → APIs
|
||||
const authToken = 'YOUR_API_KEY';
|
||||
@@ -79,11 +80,40 @@ export const main = async (
|
||||
const pdfBlob = await pdfResponse.blob();
|
||||
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
|
||||
|
||||
// Step 2: Upload the file via GraphQL multipart upload
|
||||
const fieldMetadataIdQuery = `
|
||||
query FindUploadFileFieldMetadataId {
|
||||
objects {
|
||||
edges {
|
||||
node {
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
|
||||
const response = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
query: fieldMetadataIdQuery,
|
||||
}
|
||||
});
|
||||
const result = await response.json();
|
||||
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
|
||||
|
||||
// Step 3: Upload the file via GraphQL multipart upload
|
||||
const uploadMutation = `
|
||||
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
|
||||
uploadFile(file: $file, fileFolder: $fileFolder) {
|
||||
path
|
||||
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
|
||||
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -91,12 +121,12 @@ export const main = async (
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.append('operations', JSON.stringify({
|
||||
query: uploadMutation,
|
||||
variables: { file: null, fileFolder: 'Attachment' },
|
||||
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
|
||||
}));
|
||||
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
|
||||
uploadForm.append('0', pdfFile);
|
||||
|
||||
const uploadResponse = await fetch(graphqlEndpoint, {
|
||||
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
body: uploadForm,
|
||||
@@ -108,15 +138,15 @@ export const main = async (
|
||||
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
|
||||
}
|
||||
|
||||
const filePath = uploadResult.data?.uploadFile?.path;
|
||||
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('No file path returned from upload');
|
||||
if (!fileId) {
|
||||
throw new Error('No file id returned from upload');
|
||||
}
|
||||
|
||||
// Step 3: Create the attachment linked to the company
|
||||
// Step 4: Create the attachment linked to the company
|
||||
const attachmentMutation = `
|
||||
mutation CreateAttachment($data: AttachmentCreateInput!) {
|
||||
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
|
||||
createAttachment(data: $data) {
|
||||
id
|
||||
name
|
||||
@@ -124,7 +154,7 @@ export const main = async (
|
||||
}
|
||||
`;
|
||||
|
||||
const attachmentResponse = await fetch(graphqlEndpoint, {
|
||||
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -135,8 +165,13 @@ export const main = async (
|
||||
variables: {
|
||||
data: {
|
||||
name: filename,
|
||||
fullPath: filePath,
|
||||
companyId,
|
||||
targetCompanyId: companyId,
|
||||
file: [
|
||||
{
|
||||
fileId: fileId,
|
||||
label: filename
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -156,14 +191,14 @@ export const main = async (
|
||||
|
||||
#### Para anexar a um objeto diferente
|
||||
|
||||
Substitua `companyId` pelo campo apropriado:
|
||||
Substitua `targetCompanyId` pelo campo apropriado:
|
||||
|
||||
| Objeto | Nome do Campo |
|
||||
| -------------------- | -------------------- |
|
||||
| Empresa | `companyId` |
|
||||
| Pessoa | `personId` |
|
||||
| Oportunidade | `opportunityId` |
|
||||
| Objeto personalizado | `yourCustomObjectId` |
|
||||
| Objeto | Nome do Campo |
|
||||
| -------------------- | -------------------------- |
|
||||
| Empresa | `targetCompanyId` |
|
||||
| Pessoa | `targetPersonId` |
|
||||
| Oportunidade | `targetOpportunityId` |
|
||||
| Objeto personalizado | `targetYourCustomObjectId` |
|
||||
|
||||
Atualize tanto o parâmetro da função como o objeto `variables.data` na mutação de anexo.
|
||||
|
||||
|
||||
@@ -9,79 +9,137 @@ Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, d
|
||||
|
||||
Aplicațiile vă permit să extindeți Twenty cu obiecte personalizate, câmpuri, funcții logice, abilități IA și componente UI — toate gestionate ca cod.
|
||||
|
||||
**Ce puteți face astăzi:**
|
||||
|
||||
* Definiți obiecte și câmpuri personalizate sub formă de cod (model de date gestionat)
|
||||
* Construiți funcții logice cu declanșatoare personalizate (rute HTTP, cron, evenimente ale bazei de date)
|
||||
* Definiți abilități pentru agenți de IA
|
||||
* Construiți componente front-end care se afișează în interfața Twenty
|
||||
* Implementați aceeași aplicație în mai multe spații de lucru
|
||||
|
||||
## Cerințe
|
||||
|
||||
* Node.js 24+ și Yarn 4
|
||||
* Docker (pentru serverul local de dezvoltare Twenty)
|
||||
Înainte de a începe, asigurați-vă că următoarele sunt instalate pe calculatorul dvs.:
|
||||
|
||||
## Începeți
|
||||
* **Node.js 24+** — [Descărcați aici](https://nodejs.org/)
|
||||
* **Yarn 4** — Vine împreună cu Node.js prin Corepack. Activați-l rulând `corepack enable`
|
||||
* **Docker** — [Descărcați aici](https://www.docker.com/products/docker-desktop/). Necesar pentru a rula o instanță Twenty locală. Nu este necesar dacă aveți deja un server Twenty care rulează.
|
||||
|
||||
Creați o aplicație nouă folosind generatorul oficial, apoi autentificați-vă și începeți să dezvoltați:
|
||||
## Pasul 1: Creați scheletul aplicației
|
||||
|
||||
Deschideți un terminal și rulați:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
Vi se va cere să introduceți un nume și o descriere pentru aplicația dvs. Apăsați **Enter** pentru a accepta valorile implicite.
|
||||
|
||||
Aceasta creează un folder nou numit `my-twenty-app` cu tot ce aveți nevoie.
|
||||
|
||||
<Note>
|
||||
Generatorul de schelet acceptă următoarele opțiuni:
|
||||
|
||||
* `--minimal` — generează doar fișierele esențiale, fără exemple (implicit)
|
||||
* `--exhaustive` — generează toate entitățile de exemplu
|
||||
* `--name <name>` — setează numele aplicației (omite solicitarea)
|
||||
* `--display-name <displayName>` — setează numele afișat (omite solicitarea)
|
||||
* `--description <description>` — setează descrierea (omite solicitarea)
|
||||
* `--skip-local-instance` — omite solicitarea de configurare a serverului local
|
||||
</Note>
|
||||
|
||||
## Pasul 2: Configurați o instanță Twenty locală
|
||||
|
||||
Generatorul de schelet va întreba:
|
||||
|
||||
> **Doriți să configurați o instanță Twenty locală?**
|
||||
|
||||
* **Tastați `yes`** (recomandat) — Aceasta descarcă imaginea Docker `twenty-app-dev` și pornește un server Twenty local pe portul `2020`. Asigurați-vă că Docker rulează înainte de a continua.
|
||||
* **Tastați `no`** — Alegeți această opțiune dacă aveți deja un server Twenty care rulează local.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Porniți instanța locală?" />
|
||||
</div>
|
||||
|
||||
## Pasul 3: Autentificați-vă în spațiul dvs. de lucru
|
||||
|
||||
În continuare, se va deschide o fereastră de browser cu pagina de autentificare Twenty. Autentificați-vă cu contul demo preconfigurat:
|
||||
|
||||
* **E-mail:** `tim@apple.dev`
|
||||
* **Parolă:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Ecranul de autentificare Twenty" />
|
||||
</div>
|
||||
|
||||
## Pasul 4: Autorizați aplicația
|
||||
|
||||
După autentificare, veți vedea un ecran de autorizare. Acest lucru permite aplicației dvs. să interacționeze cu spațiul dvs. de lucru.
|
||||
|
||||
Faceți clic pe **Authorize** pentru a continua.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Ecranul de autorizare Twenty CLI" />
|
||||
</div>
|
||||
|
||||
După autorizare, terminalul va confirma că totul este configurat.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Aplicația a fost creată cu succes" />
|
||||
</div>
|
||||
|
||||
## Pasul 5: Începeți dezvoltarea
|
||||
|
||||
Intrați în noul folder al aplicației și porniți serverul de dezvoltare:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
Generatorul de schelet acceptă două moduri pentru a controla ce fișiere de exemplu sunt incluse:
|
||||
Acesta monitorizează fișierele sursă, reconstruiește la fiecare modificare și sincronizează automat aplicația cu serverul Twenty local. Ar trebui să vedeți în terminal un panou de stare în timp real.
|
||||
|
||||
Pentru un output mai detaliat (jurnale de build, cereri de sincronizare, urme ale erorilor), folosiți opțiunea `--verbose`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
De aici puteți:
|
||||
<Warning>
|
||||
Modul de dezvoltare este disponibil doar pe instanțele Twenty care rulează în modul development (`NODE_ENV=development`). Instanțele de producție resping cererile de sincronizare pentru dezvoltare. Folosiți `yarn twenty deploy` pentru a implementa pe serverele de producție — vedeți [Publicarea aplicațiilor](/l/ro/developers/extend/apps/publishing) pentru detalii.
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Ieșirea terminalului în modul de dezvoltare" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## Pasul 6: Vedeți aplicația în Twenty
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
Deschideți [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) în browser. Navigați la **Settings > Apps** și selectați fila **Developer**. Ar trebui să vedeți aplicația listată la **Your Apps**:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Lista Your Apps care afișează My twenty app" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
Faceți clic pe **My twenty app** pentru a deschide **înregistrarea aplicației**. O înregistrare este un element la nivel de server care descrie aplicația — numele, identificatorul unic, acreditările OAuth și sursa (locală, npm sau arhivă tar). Aceasta există pe server, nu în interiorul unui spațiu de lucru anume. Când instalați o aplicație într-un spațiu de lucru, Twenty creează o aplicație la nivelul spațiului de lucru care face referire la această înregistrare. O singură înregistrare poate fi instalată în mai multe spații de lucru pe același server.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Detalii despre înregistrarea aplicației" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
Faceți clic pe **View installed app** pentru a vedea aplicația instalată. Fila **About** afișează versiunea curentă și opțiunile de administrare:
|
||||
|
||||
Consultați și: paginile de referință CLI pentru [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) și [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Aplicație instalată — fila About" />
|
||||
</div>
|
||||
|
||||
## Structura proiectului (generată)
|
||||
Comutați la fila **Content** pentru a vedea tot ceea ce oferă aplicația — obiecte, câmpuri, funcții logice și agenți:
|
||||
|
||||
Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Aplicație instalată — fila Content" />
|
||||
</div>
|
||||
|
||||
* Copiază o aplicație de bază minimală în `my-twenty-app/`
|
||||
* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4
|
||||
* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty`
|
||||
* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcțiile de pre-instalare și post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului.
|
||||
Totul este gata! Editați orice fișier din `src/`, iar modificările vor fi preluate automat.
|
||||
|
||||
O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel:
|
||||
Accesați [Construirea aplicațiilor](/l/ro/developers/extend/apps/building) pentru un ghid detaliat despre crearea de obiecte, funcții logice, componente front-end, abilități și altele.
|
||||
|
||||
---
|
||||
|
||||
## Structura proiectului
|
||||
|
||||
Generatorul de schelet generează următoarea structură de fișiere (afișată cu modul `--exhaustive`, care include exemple pentru fiecare tip de entitate):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -94,124 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`).
|
||||
În mod implicit (`--minimal`), sunt create doar fișierele de bază: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`. Folosiți `--exhaustive` pentru a include toate fișierele de exemplu prezentate mai sus.
|
||||
|
||||
Pe scurt:
|
||||
### Fișiere cheie
|
||||
|
||||
* **package.json**: Declară numele aplicației, versiunea, motoarele (Node 24+, Yarn 4) și adaugă `twenty-sdk` plus un script `twenty` care deleagă către CLI-ul local `twenty`. Rulați `yarn twenty help` pentru a lista toate comenzile disponibile.
|
||||
* **.gitignore**: Ignoră artefacte comune precum `node_modules`, `.yarn`, `generated/` (client tipizat), `dist/`, `build/`, foldere de coverage, fișiere jurnal și fișiere `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Blochează și configurează lanțul de instrumente Yarn 4 folosit de proiect.
|
||||
* **.nvmrc**: Fixează versiunea Node.js așteptată de proiect.
|
||||
* **.oxlintrc.json** și **tsconfig.json**: Oferă linting și configurație TypeScript pentru fișierele TypeScript ale aplicației.
|
||||
* **README.md**: Un README scurt în rădăcina aplicației, cu instrucțiuni de bază.
|
||||
* **public/**: Un folder pentru stocarea resurselor publice (imagini, fonturi, fișiere statice) care vor fi servite împreună cu aplicația dvs. Fișierele plasate aici sunt încărcate în timpul sincronizării și sunt accesibile la rulare.
|
||||
* **src/**: Locul principal unde vă definiți aplicația sub formă de cod
|
||||
| Fișier / Folder | Scop |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `package.json` | Declară numele aplicației, versiunea și dependențele. Include un script `twenty` astfel încât să puteți rula `yarn twenty help` pentru a vedea toate comenzile. |
|
||||
| `src/application-config.ts` | **Necesar.** Fișierul principal de configurare pentru aplicație. |
|
||||
| `src/roles/` | Definește roluri care controlează la ce pot avea acces funcțiile logice. |
|
||||
| `src/logic-functions/` | Funcții pe server declanșate de rute, programări cron sau evenimente din baza de date. |
|
||||
| `src/front-components/` | Componente React care se afișează în interfața Twenty. |
|
||||
| `src/objects/` | Definiții de obiecte personalizate pentru a extinde modelul de date. |
|
||||
| `src/fields/` | Câmpuri personalizate adăugate obiectelor existente. |
|
||||
| `src/views/` | Configurații pentru vizualizări salvate. |
|
||||
| `src/navigation-menu-items/` | Linkuri personalizate în bara laterală de navigare. |
|
||||
| `src/skills/` | Abilități care extind capabilitățile agenților AI ai Twenty. |
|
||||
| `src/agents/` | Agenți AI cu prompturi personalizate. |
|
||||
| `src/page-layouts/` | Machete de pagină personalizate pentru vizualizările de înregistrare. |
|
||||
| `src/__tests__/` | Teste de integrare (configurare + test exemplu). |
|
||||
| `public/` | Resurse statice (imagini, fonturi) servite împreună cu aplicația. |
|
||||
|
||||
### Detectarea entităților
|
||||
## Gestionarea remote-urilor
|
||||
|
||||
SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define<Entity>({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`:
|
||||
|
||||
| Funcție ajutătoare | Tipul entității |
|
||||
| -------------------------------- | -------------------------------------------------------------- |
|
||||
| `defineObject` | Definiții de obiecte personalizate |
|
||||
| `defineLogicFunction` | Definiții de funcții de logică |
|
||||
| `definePreInstallLogicFunction` | Funcție logică de pre-instalare (rulează înainte de instalare) |
|
||||
| `definePostInstallLogicFunction` | Funcție logică post-instalare (rulează după instalare) |
|
||||
| `defineFrontComponent` | Definiții ale componentelor de interfață |
|
||||
| `defineRole` | Definiții de rol |
|
||||
| `defineField` | Extensii de câmp pentru obiectele existente |
|
||||
| `defineView` | Definiții pentru vizualizări salvate |
|
||||
| `defineNavigationMenuItem` | Definiții pentru elemente de meniu de navigare |
|
||||
| `defineSkill` | Definiții ale abilităților agentului IA |
|
||||
|
||||
<Note>
|
||||
**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define<Entity>({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință.
|
||||
</Note>
|
||||
|
||||
Exemplu de entitate detectată:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Comenzile ulterioare vor adăuga mai multe fișiere și foldere:
|
||||
|
||||
* `yarn twenty dev` va genera automat doi clienți API tipizați în `node_modules/twenty-sdk/generated`: `CoreApiClient` (pentru datele spațiului de lucru prin `/graphql`) și `MetadataApiClient` (pentru configurarea spațiului de lucru și încărcarea fișierelor prin `/metadata`).
|
||||
* `yarn twenty entity:add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele, funcțiile, componentele front-end, rolurile, abilitățile și altele.
|
||||
|
||||
## Autentificare
|
||||
|
||||
Prima dată când rulați `yarn twenty auth:login`, vi se vor solicita:
|
||||
|
||||
* URL-ul API (implicit http://localhost:3000 sau profilul spațiului de lucru curent)
|
||||
* Cheie API
|
||||
|
||||
Acreditările dvs. sunt stocate per utilizator în `~/.twenty/config.json`. Puteți menține mai multe profiluri și comuta între ele.
|
||||
|
||||
### Gestionarea spațiilor de lucru
|
||||
Un „remote” este un server Twenty la care se conectează aplicația. În timpul configurării, generatorul de schelet creează automat unul pentru dvs. Puteți adăuga mai multe remote-uri sau comuta între ele oricând.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
După ce ați schimbat spațiul de lucru cu `yarn twenty auth:switch`, toate comenzile ulterioare vor folosi implicit acel spațiu de lucru. Îl puteți totuși suprascrie temporar cu `--workspace <name>`.
|
||||
Acreditările dvs. sunt stocate în `~/.twenty/config.json`.
|
||||
|
||||
## Server local de dezvoltare (`yarn twenty server`)
|
||||
|
||||
CLI-ul poate gestiona un server Twenty local care rulează în Docker. Acesta este același server pornit automat când creați scheletul unei aplicații cu `create-twenty-app`, dar îl puteți gestiona și manual.
|
||||
|
||||
### Pornirea serverului
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
Aceasta descarcă imaginea Docker `twentycrm/twenty-app-dev:latest` (dacă nu este deja prezentă), creează un container numit `twenty-app-dev` și îl pornește pe portul **2020**. CLI-ul așteaptă până când serverul trece verificarea de integritate înainte de a reveni.
|
||||
|
||||
Sunt create două volume Docker pentru a păstra datele între reporniri:
|
||||
|
||||
* `twenty-app-dev-data` — bază de date PostgreSQL
|
||||
* `twenty-app-dev-storage` — stocare fișiere
|
||||
|
||||
Dacă portul 2020 este deja utilizat, puteți porni pe un alt port:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
CLI-ul configurează automat `NODE_PORT` și `SERVER_URL` interne ale containerului pentru a se potrivi cu portul ales, astfel încât funcțiile logice, OAuth și toată rețeaua internă să funcționeze corect.
|
||||
|
||||
După pornire, serverul este înregistrat automat ca remote `local` în configurația CLI.
|
||||
|
||||
### Verificarea stării serverului
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
Afișează dacă serverul rulează, URL-ul său și acreditările implicite de autentificare (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### Vizualizarea jurnalelor serverului
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
Transmite în flux jurnalele containerului. Folosiți `--lines` pentru a controla câte linii recente să fie afișate:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### Oprirea serverului
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
Oprește containerul. Datele dvs. sunt păstrate în volumele Docker — următoarea comandă `start` reia de unde ați rămas.
|
||||
|
||||
### Resetarea serverului
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
Elimină containerul **și** șterge ambele volume Docker, ștergând toate datele. Următoarea comandă `start` creează o instanță nouă.
|
||||
|
||||
<Note>
|
||||
Serverul necesită ca **Docker** să ruleze. Dacă vedeți eroarea "Docker not running", asigurați-vă că Docker Desktop (sau demonul Docker) este pornit.
|
||||
</Note>
|
||||
|
||||
### Referință pentru comenzi
|
||||
|
||||
| Comandă | Descriere |
|
||||
| -------------------------------------- | ------------------------------------------------------------- |
|
||||
| `yarn twenty server start` | Pornește serverul local (descarcă imaginea dacă este necesar) |
|
||||
| `yarn twenty server start --port 3030` | Pornește pe un port personalizat |
|
||||
| `yarn twenty server stop` | Oprește serverul (păstrează datele) |
|
||||
| `yarn twenty server status` | Afișează starea serverului, URL-ul și acreditările |
|
||||
| `yarn twenty server logs` | Transmite în flux jurnalele serverului |
|
||||
| `yarn twenty server logs --lines 100` | Afișează ultimele 100 de linii de jurnal |
|
||||
| `yarn twenty server reset` | Șterge toate datele și pornește de la zero |
|
||||
|
||||
## CI cu GitHub Actions
|
||||
|
||||
Scaffolderul generează un workflow GitHub Actions gata de utilizare în `.github/workflows/ci.yml`. Rulează automat testele de integrare la fiecare push pe `main` și la pull request-uri.
|
||||
|
||||
Workflow-ul:
|
||||
|
||||
1. Preia codul
|
||||
2. Pornește un server Twenty temporar folosind acțiunea `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
|
||||
3. Instalează dependențele cu `yarn install --immutable`
|
||||
4. Rulează `yarn test` cu `TWENTY_API_URL` și `TWENTY_API_KEY` injectate din rezultatele acțiunii
|
||||
|
||||
```yaml .github/workflows/ci.yml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run integration tests
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
```
|
||||
|
||||
Nu trebuie să configurați niciun secret — acțiunea `spawn-twenty-docker-image` pornește un server Twenty efemer direct în runner și oferă detaliile de conectare. Secretul `GITHUB_TOKEN` este furnizat automat de GitHub.
|
||||
|
||||
Pentru a fixa o versiune Twenty specifică în loc de `latest`, modificați variabila de mediu `TWENTY_VERSION` din partea de sus a workflow-ului.
|
||||
|
||||
## Configurare manuală (fără generator)
|
||||
|
||||
Deși recomandăm utilizarea `create-twenty-app` pentru cea mai bună experiență de început, puteți configura și un proiect manual. Nu instalați CLI-ul global. În schimb, adăugați `twenty-sdk` ca dependență locală și conectați un singur script în package.json-ul dvs.:
|
||||
Dacă preferați să configurați totul manual în loc să folosiți `create-twenty-app`, o puteți face în doi pași.
|
||||
|
||||
**1. Adăugați `twenty-sdk` și `twenty-client-sdk` ca dependențe:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Apoi adăugați un script `twenty`:
|
||||
**2. Adăugați un script `twenty` în `package.json`:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -221,25 +393,19 @@ Apoi adăugați un script `twenty`:
|
||||
}
|
||||
```
|
||||
|
||||
Acum poți rula toate comenzile prin `yarn twenty <command>`, de ex. `yarn twenty dev`, `yarn twenty help`, etc.
|
||||
Acum puteți rula `yarn twenty dev`, `yarn twenty help` și toate celelalte comenzi.
|
||||
|
||||
## Cum să folosești o instanță Twenty locală
|
||||
|
||||
Dacă rulezi deja local o instanță Twenty (de exemplu prin `npx nx start twenty-server`), te poți conecta la ea în loc să folosești Docker:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
Nu instalați `twenty-sdk` global. Folosiți-l întotdeauna ca dependență locală de proiect, astfel încât fiecare proiect să își poată fixa propria versiune.
|
||||
</Note>
|
||||
|
||||
## Depanare
|
||||
|
||||
* Erori de autentificare: rulați `yarn twenty auth:login` și asigurați-vă că cheia API are permisiunile necesare.
|
||||
* Nu se poate conecta la server: verificați URL-ul API și că serverul Twenty este accesibil.
|
||||
* Tipuri sau client lipsă/învechite: repornește `yarn twenty dev` — acesta generează automat clientul tipizat.
|
||||
* Modul dev nu sincronizează: asigură-te că `yarn twenty dev` rulează și că modificările nu sunt ignorate de mediul tău.
|
||||
Dacă întâmpinați probleme:
|
||||
|
||||
Canal de ajutor pe Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
* Asigurați-vă că Docker rulează înainte de a porni scaffolderul cu o instanță locală.
|
||||
* Asigurați-vă că folosiți **Node.js 24+** (`node -v` pentru verificare).
|
||||
* Asigurați-vă că **Corepack este activat** (`corepack enable`) astfel încât Yarn 4 să fie disponibil.
|
||||
* Încercați să ștergeți `node_modules` și să rulați din nou `yarn install` dacă dependențele par deteriorate.
|
||||
|
||||
Încă aveți probleme? Cereți ajutor pe [Discordul Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
|
||||
|
||||
@@ -4,15 +4,75 @@ description: Distribuie aplicația ta Twenty în marketplace sau implementeaz-o
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare.
|
||||
Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, dar este încă în dezvoltare.
|
||||
</Warning>
|
||||
|
||||
## Prezentare generală
|
||||
|
||||
După ce aplicația ta este [construită și testată local](/l/ro/developers/extend/apps/building), ai două căi pentru distribuire:
|
||||
|
||||
* **Implementați o arhivă tar** — încărcați aplicația direct pe un server Twenty anume pentru uz intern sau privat.
|
||||
* **Publică pe npm** — listează aplicația ta în marketplace-ul Twenty pentru ca orice spațiu de lucru să o poată descoperi și instala.
|
||||
* **Trimite un tarball** — implementează aplicația ta pe un server Twenty specific pentru utilizare internă, fără a o face disponibilă public.
|
||||
|
||||
Ambele căi pornesc din aceeași etapă de **build**.
|
||||
|
||||
## Construirea aplicației
|
||||
|
||||
Rulează comanda `build` pentru a compila aplicația și a genera un `manifest.json` pregătit pentru distribuire:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
Aceasta compilează sursele TypeScript, transpilează funcțiile de logică și componentele de front-end și scrie totul în `.twenty/output/`. Adaugă `--tarball` pentru a produce și un pachet `.tgz` pentru distribuire manuală sau pentru comanda de deploy.
|
||||
|
||||
## Implementare pe un server (tarball)
|
||||
|
||||
Pentru aplicațiile pe care nu le dorești disponibile public — instrumente proprietare, integrări doar pentru enterprise sau build-uri experimentale — poți implementa un tarball direct pe un server Twenty.
|
||||
|
||||
### Cerințe
|
||||
|
||||
Înainte de implementare, ai nevoie de un remote configurat care să indice serverul țintă. Remote-urile stochează local URL-ul serverului și credențialele de autentificare în `~/.twenty/config.json`.
|
||||
|
||||
Adaugă un remote:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Implementare
|
||||
|
||||
Construiește și încarcă aplicația ta pe server într-un singur pas:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Partajarea unei aplicații implementate
|
||||
|
||||
Aplicațiile tarball nu sunt listate în marketplace-ul public, astfel încât alte spații de lucru de pe același server nu le vor descoperi prin navigare. Pentru a partaja o aplicație implementată:
|
||||
|
||||
1. Mergi la **Setări > Aplicații > Înregistrări** și deschide aplicația ta
|
||||
2. În fila **Distribuție**, fă clic pe **Copiază linkul de partajare**
|
||||
3. Partajează acest link cu utilizatori din alte spații de lucru — îi duce direct la pagina de instalare a aplicației
|
||||
|
||||
Linkul de partajare folosește URL-ul de bază al serverului (fără niciun subdomeniu de spațiu de lucru), astfel încât funcționează pentru orice spațiu de lucru de pe server.
|
||||
|
||||
<Warning>
|
||||
Partajarea aplicațiilor private este o funcționalitate Enterprise. Mergi la [Setări > Panou de administrare > Enterprise](/settings/admin-panel#enterprise) pentru a o activa.
|
||||
</Warning>
|
||||
|
||||
### Gestionarea versiunilor
|
||||
|
||||
Pentru a lansa o actualizare:
|
||||
|
||||
1. Actualizează câmpul `version` din `package.json`
|
||||
2. Rulează `yarn twenty deploy` (sau `yarn twenty deploy --remote production`)
|
||||
3. Spațiile de lucru care au aplicația instalată vor vedea actualizarea disponibilă în setările lor
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## Publicarea pe npm
|
||||
|
||||
@@ -21,29 +81,69 @@ Publicarea pe npm face ca aplicația ta să poată fi descoperită în marketpla
|
||||
### Cerințe
|
||||
|
||||
* Un cont [npm](https://www.npmjs.com)
|
||||
* Numele pachetului tău **trebuie** să folosească prefixul `twenty-app-` (de ex., `twenty-app-postcard-sender`)
|
||||
* Cuvântul cheie `twenty-app` din array-ul `keywords` al fișierului `package.json` (deja inclus când inițializezi proiectul cu `create-twenty-app`)
|
||||
|
||||
### Pași
|
||||
|
||||
1. **Construiește-ți aplicația** — CLI compilează sursele TypeScript și generează manifestul aplicației:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Publică pe npm** — publică pachetul construit în registrul npm:
|
||||
### Metadate pentru marketplace
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
Configurația `defineApplication()` acceptă câmpuri opționale care controlează modul în care aplicația ta apare în marketplace. Folosește `logoUrl` și `screenshots` pentru a face referire la imaginile din folderul `public/`:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Descoperire automată
|
||||
Vezi [acordeonul defineApplication](/l/ro/developers/extend/apps/building#defineentity-functions) din pagina Building Apps pentru lista completă de câmpuri ale marketplace-ului (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
Pachetele cu prefixul `twenty-app-` sunt descoperite automat de catalogul marketplace-ului Twenty. După publicare, aplicația ta apare în marketplace în câteva minute — fără înregistrare manuală sau aprobare necesară.
|
||||
### Publicare
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
Pentru a publica sub un dist-tag specific (de ex., `beta` sau `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### Cum funcționează descoperirea în marketplace
|
||||
|
||||
Serverul Twenty sincronizează catalogul marketplace-ului din registrul npm **la fiecare oră**.
|
||||
|
||||
Poți declanșa sincronizarea imediat, în loc să aștepți:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
Metadatele afișate în marketplace provin din configurația `defineApplication()` — câmpuri precum `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` și `termsUrl`.
|
||||
|
||||
<Note>
|
||||
Dacă aplicația ta nu definește un `aboutDescription` în `defineApplication()`, piața va folosi automat fișierul `README.md` al pachetului tău de pe npm drept conținut pentru pagina Despre. Acest lucru înseamnă că poți menține un singur README atât pentru npm, cât și pentru piața Twenty. Dacă vrei o descriere diferită în piață, setează explicit `aboutDescription`.
|
||||
</Note>
|
||||
|
||||
### Publicare CI
|
||||
|
||||
Proiectul generat include un workflow GitHub Actions care publică la fiecare lansare. Acesta rulează `app:build`, apoi `npm publish --provenance` din rezultatul build-ului:
|
||||
Folosește acest workflow GitHub Actions pentru a publica automat la fiecare release (folosește [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
Pentru alte sisteme CI (GitLab CI, CircleCI etc.), se aplică aceleași trei comenzi: `yarn install`, `npx twenty build`, apoi `npm publish` din `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**npm provenance** este opțională, dar recomandată. Publicarea cu `--provenance` adaugă un badge de încredere la listarea ta în npm, permițând utilizatorilor să verifice că pachetul a fost construit dintr-un commit specific într-un pipeline CI public. Vezi [documentația npm provenance](https://docs.npmjs.com/generating-provenance-statements) pentru instrucțiuni de configurare.
|
||||
</Tip>
|
||||
|
||||
## Distribuire internă
|
||||
|
||||
Pentru aplicațiile pe care nu le dorești disponibile public — instrumente proprietare, integrări doar pentru enterprise sau build-uri experimentale — poți trimite un tarball direct pe un server Twenty.
|
||||
|
||||
### Trimite un tarball
|
||||
|
||||
Construiește-ți aplicația și implementeaz-o pe un server specific, într-un singur pas:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
Orice spațiu de lucru de pe acel server poate apoi instala și actualiza aplicația din pagina de setări **Applications**.
|
||||
|
||||
### Gestionarea versiunilor
|
||||
|
||||
Pentru a lansa o actualizare:
|
||||
|
||||
1. Actualizează câmpul `version` din `package.json`
|
||||
2. Trimite un tarball nou cu `npx twenty publish --server <server-url>`
|
||||
3. Spațiile de lucru de pe acel server vor vedea actualizarea disponibilă în setările lor
|
||||
Pentru alte sisteme CI (GitLab CI, CircleCI etc.), se aplică aceleași trei comenzi: `yarn install`, `yarn twenty build`, apoi `npm publish` din `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
Aplicațiile interne sunt limitate la serverul pe care sunt trimise. Acestea nu vor apărea în marketplace-ul public și nu pot fi instalate de spațiile de lucru de pe alte servere.
|
||||
**npm provenance** este opțională, dar recomandată. Publicarea cu `--provenance` adaugă un badge de încredere la listarea ta în npm, permițând utilizatorilor să verifice că pachetul a fost construit dintr-un commit specific într-un pipeline CI public. Vezi [documentația npm provenance](https://docs.npmjs.com/generating-provenance-statements) pentru instrucțiuni de configurare.
|
||||
</Note>
|
||||
|
||||
## Categorii de aplicații
|
||||
## Instalarea aplicațiilor
|
||||
|
||||
Twenty organizează aplicațiile în trei categorii, în funcție de modul în care sunt distribuite:
|
||||
După ce o aplicație este publicată (npm) sau implementată (tarball), spațiile de lucru o pot instala prin interfața utilizatorului (UI).
|
||||
|
||||
| Categorie | Cum funcționează | Vizibilă în marketplace? |
|
||||
| -------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
||||
| **Dezvoltare** | Aplicații în modul de dezvoltare local, rulate prin `yarn twenty dev`. Folosite pentru construire și testare. | Nu |
|
||||
| **Publicat** | Aplicații publicate pe npm cu prefixul `twenty-app-`. Listate în marketplace pentru ca orice spațiu de lucru să le poată instala. | Da |
|
||||
| **Intern** | Aplicații implementate prin tarball pe un server specific. Disponibile doar pentru spațiile de lucru de pe acel server. | Nu |
|
||||
Mergi la pagina **Setări > Aplicații** din Twenty, unde pot fi parcurse și instalate atât aplicațiile din marketplace, cât și cele implementate prin tarball.
|
||||
|
||||
<Tip>
|
||||
Pornește în modul **Dezvoltare** în timp ce îți construiești aplicația. Când este gata, alege **Publicat** (npm) pentru distribuire largă sau **Intern** (tarball) pentru implementare privată.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
Poți instala aplicații și din linia de comandă:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||
@@ -55,11 +55,12 @@ export const main = async (
|
||||
params: { companyId: string },
|
||||
) => {
|
||||
const { companyId } = params;
|
||||
|
||||
// Replace with your Twenty GraphQL endpoint
|
||||
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
|
||||
// Cloud: https://api.twenty.com/graphql
|
||||
// Self-hosted: https://your-domain.com/graphql
|
||||
const graphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
|
||||
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
|
||||
|
||||
// Replace with your API key from Settings → APIs
|
||||
const authToken = 'YOUR_API_KEY';
|
||||
@@ -79,11 +80,40 @@ export const main = async (
|
||||
const pdfBlob = await pdfResponse.blob();
|
||||
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
|
||||
|
||||
// Step 2: Upload the file via GraphQL multipart upload
|
||||
const fieldMetadataIdQuery = `
|
||||
query FindUploadFileFieldMetadataId {
|
||||
objects {
|
||||
edges {
|
||||
node {
|
||||
nameSingular
|
||||
fieldsList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
|
||||
const response = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`
|
||||
},
|
||||
body: {
|
||||
query: fieldMetadataIdQuery,
|
||||
}
|
||||
});
|
||||
const result = await response.json();
|
||||
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
|
||||
|
||||
// Step 3: Upload the file via GraphQL multipart upload
|
||||
const uploadMutation = `
|
||||
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
|
||||
uploadFile(file: $file, fileFolder: $fileFolder) {
|
||||
path
|
||||
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
|
||||
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -91,12 +121,12 @@ export const main = async (
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.append('operations', JSON.stringify({
|
||||
query: uploadMutation,
|
||||
variables: { file: null, fileFolder: 'Attachment' },
|
||||
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
|
||||
}));
|
||||
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
|
||||
uploadForm.append('0', pdfFile);
|
||||
|
||||
const uploadResponse = await fetch(graphqlEndpoint, {
|
||||
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${authToken}` },
|
||||
body: uploadForm,
|
||||
@@ -108,15 +138,15 @@ export const main = async (
|
||||
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
|
||||
}
|
||||
|
||||
const filePath = uploadResult.data?.uploadFile?.path;
|
||||
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('No file path returned from upload');
|
||||
if (!fileId) {
|
||||
throw new Error('No file id returned from upload');
|
||||
}
|
||||
|
||||
// Step 3: Create the attachment linked to the company
|
||||
// Step 4: Create the attachment linked to the company
|
||||
const attachmentMutation = `
|
||||
mutation CreateAttachment($data: AttachmentCreateInput!) {
|
||||
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
|
||||
createAttachment(data: $data) {
|
||||
id
|
||||
name
|
||||
@@ -124,7 +154,7 @@ export const main = async (
|
||||
}
|
||||
`;
|
||||
|
||||
const attachmentResponse = await fetch(graphqlEndpoint, {
|
||||
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -135,8 +165,13 @@ export const main = async (
|
||||
variables: {
|
||||
data: {
|
||||
name: filename,
|
||||
fullPath: filePath,
|
||||
companyId,
|
||||
targetCompanyId: companyId,
|
||||
file: [
|
||||
{
|
||||
fileId: fileId,
|
||||
label: filename
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -156,14 +191,14 @@ export const main = async (
|
||||
|
||||
#### Pentru a atașa la un alt obiect
|
||||
|
||||
Înlocuiți `companyId` cu câmpul corespunzător:
|
||||
Înlocuiți `targetCompanyId` cu câmpul corespunzător:
|
||||
|
||||
| Obiect | Nume câmp |
|
||||
| ------------------- | -------------------- |
|
||||
| Companie | `companyId` |
|
||||
| Persoană | `personId` |
|
||||
| Oportunitate | `opportunityId` |
|
||||
| Obiect personalizat | `yourCustomObjectId` |
|
||||
| Obiect | Nume câmp |
|
||||
| ------------------- | -------------------------- |
|
||||
| Companie | `targetCompanyId` |
|
||||
| Persoană | `targetPersonId` |
|
||||
| Oportunitate | `targetOpportunityId` |
|
||||
| Obiect personalizat | `targetYourCustomObjectId` |
|
||||
|
||||
Actualizați atât parametrul funcției, cât și obiectul `variables.data` în mutația de atașare.
|
||||
|
||||
|
||||
@@ -4,84 +4,142 @@ description: Создайте своё первое приложение Twenty
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться.
|
||||
Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться.
|
||||
</Warning>
|
||||
|
||||
Приложения позволяют расширять Twenty с помощью пользовательских объектов, полей, логических функций, навыков ИИ и UI-компонентов — всё это управляется как код.
|
||||
|
||||
**Что вы можете делать уже сегодня:**
|
||||
|
||||
* Определяйте пользовательские объекты и поля в виде кода (управляемая модель данных)
|
||||
* Создавайте логические функции с пользовательскими триггерами (HTTP-маршруты, cron, события базы данных)
|
||||
* Определяйте навыки для ИИ-агентов
|
||||
* Создавайте фронтенд-компоненты, которые отображаются внутри интерфейса Twenty
|
||||
* Развёртывайте одно и то же приложение в нескольких рабочих пространствах
|
||||
|
||||
## Требования
|
||||
|
||||
* Node.js 24+ и Yarn 4
|
||||
* Docker (для локального сервера разработки Twenty)
|
||||
Прежде чем начать, убедитесь, что на вашем компьютере установлено следующее:
|
||||
|
||||
## Начало работы
|
||||
* **Node.js 24+** — [Скачать здесь](https://nodejs.org/)
|
||||
* **Yarn 4** — Поставляется вместе с Node.js через Corepack. Включите его, выполнив `corepack enable`
|
||||
* **Docker** — [Скачать здесь](https://www.docker.com/products/docker-desktop/). Требуется для запуска локального экземпляра Twenty. Не требуется, если у вас уже запущен сервер Twenty.
|
||||
|
||||
Создайте новое приложение с помощью официального генератора, затем выполните аутентификацию и начните разработку:
|
||||
## Шаг 1: Сгенерируйте каркас приложения
|
||||
|
||||
Откройте терминал и выполните:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Scaffold a new app (includes all examples by default)
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
|
||||
# Start dev mode: automatically syncs local changes to your workspace
|
||||
Вам будет предложено ввести имя и описание вашего приложения. Нажмите **Enter**, чтобы принять значения по умолчанию.
|
||||
|
||||
Будет создана новая папка `my-twenty-app` со всем необходимым.
|
||||
|
||||
<Note>
|
||||
Генератор поддерживает следующие флаги:
|
||||
|
||||
* `--minimal` — сгенерировать только основные файлы, без примеров (по умолчанию)
|
||||
* `--exhaustive` — сгенерировать все примеры сущностей
|
||||
* `--name <name>` — задать имя приложения (пропускает запрос)
|
||||
* `--display-name <displayName>` — задать отображаемое имя (пропускает запрос)
|
||||
* `--description <description>` — задать описание (пропускает запрос)
|
||||
* `--skip-local-instance` — пропустить запрос на настройку локального сервера
|
||||
</Note>
|
||||
|
||||
## Шаг 2: Настройте локальный экземпляр Twenty
|
||||
|
||||
Скэффолдер спросит:
|
||||
|
||||
> **Хотите настроить локальный экземпляр Twenty?**
|
||||
|
||||
* **Введите `yes`** (рекомендуется) — это скачает Docker-образ `twenty-app-dev` и запустит локальный сервер Twenty на порту `2020`. Перед продолжением убедитесь, что Docker запущен.
|
||||
* **Введите `no`** — выберите это, если у вас уже запущен локальный сервер Twenty.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Запустить локальный экземпляр?" />
|
||||
</div>
|
||||
|
||||
## Шаг 3: Войдите в своё рабочее пространство
|
||||
|
||||
Затем откроется окно браузера со страницей входа в Twenty. Войдите, используя предварительно созданную демонстрационную учётную запись:
|
||||
|
||||
* **Электронная почта:** `tim@apple.dev`
|
||||
* **Пароль:** `tim@apple.dev`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Экран входа в Twenty" />
|
||||
</div>
|
||||
|
||||
## Шаг 4: Авторизуйте приложение
|
||||
|
||||
После входа вы увидите экран авторизации. Это позволит вашему приложению взаимодействовать с вашим рабочим пространством.
|
||||
|
||||
Нажмите **Authorize**, чтобы продолжить.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Экран авторизации Twenty CLI" />
|
||||
</div>
|
||||
|
||||
После авторизации в терминале появится подтверждение, что всё настроено.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Каркас приложения успешно создан" />
|
||||
</div>
|
||||
|
||||
## Шаг 5: Начните разработку
|
||||
|
||||
Перейдите в папку вашего нового приложения и запустите сервер разработки:
|
||||
|
||||
```bash filename="Terminal"
|
||||
cd my-twenty-app
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
Генератор каркаса поддерживает два режима для управления тем, какие файлы-примеры включаются:
|
||||
Он отслеживает исходные файлы, пересобирает при каждом изменении и автоматически синхронизирует ваше приложение с локальным сервером Twenty. В терминале должна появиться панель текущего статуса.
|
||||
|
||||
Для более подробного вывода (журналы сборки, запросы синхронизации, трассировки ошибок) используйте флаг `--verbose`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
npx create-twenty-app@latest my-app --minimal
|
||||
yarn twenty dev --verbose
|
||||
```
|
||||
|
||||
Отсюда вы можете:
|
||||
<Warning>
|
||||
Режим разработки доступен только на экземплярах Twenty, запущенных в режиме разработки (`NODE_ENV=development`). Экземпляры в продакшене отклоняют запросы синхронизации из режима разработки. Используйте `yarn twenty deploy` для развёртывания на продакшен-серверах — подробности см. в разделе [Публикация приложений](/l/ru/developers/extend/apps/publishing).
|
||||
</Warning>
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Вывод терминала в режиме разработки" />
|
||||
</div>
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
## Шаг 6: Посмотрите своё приложение в Twenty
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
Откройте [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) в браузере. Перейдите в **Settings > Apps** и выберите вкладку **Developer**. Вы должны увидеть своё приложение в разделе **Your Apps**:
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Список Your Apps с приложением My twenty app" />
|
||||
</div>
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
Нажмите **My twenty app**, чтобы открыть его **регистрацию приложения**. Регистрация — это запись на уровне сервера, описывающая ваше приложение: его имя, уникальный идентификатор, учётные данные OAuth и источник (локальный, npm или tarball). Она хранится на сервере, а не внутри какого-либо конкретного рабочего пространства. Когда вы устанавливаете приложение в рабочее пространство, Twenty создаёт привязанное к рабочему пространству **приложение**, которое ссылается на эту регистрацию. Одну и ту же регистрацию можно установить в нескольких рабочих пространствах на одном сервере.
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Сведения о регистрации приложения" />
|
||||
</div>
|
||||
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
Нажмите **View installed app**, чтобы посмотреть установленное приложение. Вкладка **About** показывает текущую версию и параметры управления:
|
||||
|
||||
Смотрите также: страницы справки CLI для [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) и [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Установленное приложение — вкладка About" />
|
||||
</div>
|
||||
|
||||
## Структура проекта (сгенерированного)
|
||||
Переключитесь на вкладку **Content**, чтобы увидеть всё, что предоставляет ваше приложение: объекты, поля, логические функции и агенты:
|
||||
|
||||
Когда вы запускаете `npx create-twenty-app@latest my-twenty-app`, генератор:
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Установленное приложение — вкладка Content" />
|
||||
</div>
|
||||
|
||||
* Копирует минимальное базовое приложение в `my-twenty-app/`
|
||||
* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4
|
||||
* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty`
|
||||
* Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, предустановочную и послеустановочную функции), а также примерные файлы в зависимости от выбранного режима создания каркаса
|
||||
Готово! Отредактируйте любой файл в `src/`, и изменения будут подхвачены автоматически.
|
||||
|
||||
Сгенерированное с помощью каркаса приложение с режимом по умолчанию `--exhaustive` выглядит так:
|
||||
Перейдите к разделу [Создание приложений](/l/ru/developers/extend/apps/building) за подробным руководством по созданию объектов, логических функций, фронтенд-компонентов, навыков и многого другого.
|
||||
|
||||
---
|
||||
|
||||
## Структура проекта
|
||||
|
||||
Скэффолдер генерирует следующую структуру файлов (показано в режиме `--exhaustive`, который включает примеры для каждого типа сущностей):
|
||||
|
||||
```text filename="my-twenty-app/"
|
||||
my-twenty-app/
|
||||
@@ -94,124 +152,238 @@ my-twenty-app/
|
||||
install-state.gz
|
||||
.oxlintrc.json
|
||||
tsconfig.json
|
||||
tsconfig.spec.json # TypeScript config for tests
|
||||
vitest.config.ts # Vitest test runner configuration
|
||||
LLMS.md
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
.github/
|
||||
└── workflows/
|
||||
└── ci.yml # GitHub Actions CI workflow
|
||||
public/ # Public assets (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Required — main application configuration
|
||||
├── __tests__/
|
||||
│ ├── setup-test.ts # Test setup (server health check, config)
|
||||
│ └── app-install.integration-test.ts # Example integration test
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
|
||||
│ ├── pre-install.ts # Runs before installation
|
||||
│ └── post-install.ts # Runs after installation
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── page-layouts/
|
||||
│ └── example-record-page-layout.ts # Example page layout with front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
├── skills/
|
||||
│ └── example-skill.ts # Example AI agent skill definition
|
||||
└── agents/
|
||||
└── example-agent.ts # Example AI agent definition
|
||||
```
|
||||
|
||||
С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`).
|
||||
По умолчанию (`--minimal`) создаются только основные файлы: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`. Используйте `--exhaustive`, чтобы включить все показанные выше файлы-примеры.
|
||||
|
||||
В общих чертах:
|
||||
### Ключевые файлы
|
||||
|
||||
* **package.json**: Объявляет имя приложения, версию, движки (Node 24+, Yarn 4) и добавляет `twenty-sdk`, а также скрипт `twenty`, который делегирует выполнение локальному CLI `twenty`. Выполните `yarn twenty help`, чтобы вывести список всех доступных команд.
|
||||
* **.gitignore**: Игнорирует распространённые артефакты, такие как `node_modules`, `.yarn`, `generated/` (типизированный клиент), `dist/`, `build/`, каталоги coverage, файлы журналов и файлы `.env*`.
|
||||
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Фиксируют и настраивают используемый в проекте инструментарий Yarn 4.
|
||||
* **.nvmrc**: Фиксирует версию Node.js, ожидаемую проектом.
|
||||
* **.oxlintrc.json** и **tsconfig.json**: Обеспечивают линтинг и конфигурацию TypeScript для исходников вашего приложения на TypeScript.
|
||||
* **README.md**: Короткий README в корне приложения с базовыми инструкциями.
|
||||
* **public/**: Папка для хранения общедоступных ресурсов (изображений, шрифтов, статических файлов), которые будут отдаваться вашим приложением. Файлы, размещённые здесь, загружаются во время синхронизации и доступны во время выполнения.
|
||||
* **src/**: Основное место, где вы определяете приложение как код
|
||||
| Файл / Папка | Назначение |
|
||||
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `package.json` | Содержит имя, версию и зависимости вашего приложения. Содержит скрипт `twenty`, чтобы вы могли выполнить `yarn twenty help` и увидеть все команды. |
|
||||
| `src/application-config.ts` | **Обязательно.** Основной файл конфигурации для вашего приложения. |
|
||||
| `src/roles/` | Определяет роли, которые контролируют доступ логических функций. |
|
||||
| `src/logic-functions/` | Серверные функции, запускаемые маршрутами, расписаниями cron или событиями базы данных. |
|
||||
| `src/front-components/` | Компоненты React, которые отображаются внутри интерфейса Twenty. |
|
||||
| `src/objects/` | Пользовательские определения объектов для расширения вашей модели данных. |
|
||||
| `src/fields/` | Пользовательские поля, добавляемые к существующим объектам. |
|
||||
| `src/views/` | Конфигурации сохранённых представлений. |
|
||||
| `src/navigation-menu-items/` | Пользовательские ссылки в боковой навигации. |
|
||||
| `src/skills/` | Навыки, расширяющие возможности ИИ-агентов Twenty. |
|
||||
| `src/agents/` | ИИ-агенты с пользовательскими промптами. |
|
||||
| `src/page-layouts/` | Пользовательские макеты страниц для представлений записей. |
|
||||
| `src/__tests__/` | Интеграционные тесты (настройка + пример теста). |
|
||||
| `public/` | Статические ресурсы (изображения, шрифты), обслуживаемые вместе с вашим приложением. |
|
||||
|
||||
### Обнаружение сущностей
|
||||
## Управление удалёнными серверами
|
||||
|
||||
SDK обнаруживает сущности, разбирая ваши файлы TypeScript в поисках вызовов **`export default define<Entity>({...})`**. Для каждого типа сущности существует соответствующая вспомогательная функция, экспортируемая из `twenty-sdk`:
|
||||
|
||||
| Вспомогательная функция | Тип сущности |
|
||||
| -------------------------------- | ------------------------------------------------------------------ |
|
||||
| `defineObject` | Определения пользовательских объектов |
|
||||
| `defineLogicFunction` | Определения логических функций |
|
||||
| `definePreInstallLogicFunction` | Предустановочная логическая функция (запускается до установки) |
|
||||
| `definePostInstallLogicFunction` | Послеустановочная логическая функция (запускается после установки) |
|
||||
| `defineFrontComponent` | Определения компонентов фронтенда |
|
||||
| `defineRole` | Определения ролей |
|
||||
| `defineField` | Расширения полей для существующих объектов |
|
||||
| `defineView` | Определения сохранённых представлений |
|
||||
| `defineNavigationMenuItem` | Определения пунктов меню навигации |
|
||||
| `defineSkill` | Определения навыков агента ИИ |
|
||||
|
||||
<Note>
|
||||
**Имена файлов заданы гибко.** Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона `export default define<Entity>({...})`. Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, `logic-functions/`, `roles/`) — это лишь соглашение для организации кода, а не требование.
|
||||
</Note>
|
||||
|
||||
Пример обнаруженной сущности:
|
||||
|
||||
```typescript
|
||||
// This file can be named anything and placed anywhere in src/
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: '...',
|
||||
nameSingular: 'postCard',
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
|
||||
Позднее команды добавят больше файлов и папок:
|
||||
|
||||
* `yarn twenty dev` автоматически сгенерирует два типизированных клиента API в `node_modules/twenty-sdk/generated`: `CoreApiClient` (для данных рабочего пространства через `/graphql`) и `MetadataApiClient` (для конфигурации рабочего пространства и загрузки файлов через `/metadata`).
|
||||
* `yarn twenty entity:add` добавит файлы определений сущностей в `src/` для ваших пользовательских объектов, функций, фронтенд-компонентов, ролей, навыков и многого другого.
|
||||
|
||||
## Аутентификация
|
||||
|
||||
При первом запуске `yarn twenty auth:login` вам будет предложено указать:
|
||||
|
||||
* URL API (по умолчанию http://localhost:3000 или текущий профиль рабочего пространства)
|
||||
* Ключ API
|
||||
|
||||
Ваши учётные данные хранятся для каждого пользователя в `~/.twenty/config.json`. Вы можете хранить несколько профилей и переключаться между ними.
|
||||
|
||||
### Управление рабочими пространствами
|
||||
Remote — это сервер Twenty, к которому подключается ваше приложение. Во время настройки скэффолдер автоматически создаст его для вас. Вы можете в любой момент добавлять новые remotes или переключаться между ними.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Login interactively (recommended)
|
||||
yarn twenty auth:login
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Login to a specific workspace profile
|
||||
yarn twenty auth:login --workspace my-custom-workspace
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# List all configured workspaces
|
||||
yarn twenty auth:list
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# Switch the default workspace (interactive)
|
||||
yarn twenty auth:switch
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch to a specific workspace
|
||||
yarn twenty auth:switch production
|
||||
|
||||
# Check current authentication status
|
||||
yarn twenty auth:status
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
После переключения рабочего пространства с помощью `yarn twenty auth:switch` все последующие команды по умолчанию будут использовать это рабочее пространство. Вы по-прежнему можете временно переопределить это с помощью `--workspace <name>`.
|
||||
Ваши учётные данные хранятся в `~/.twenty/config.json`.
|
||||
|
||||
## Локальный сервер разработки (`yarn twenty server`)
|
||||
|
||||
CLI может управлять локальным сервером Twenty, запущенным в Docker. Это тот же сервер, который автоматически запускается при создании каркаса приложения с помощью `create-twenty-app`, но им можно управлять и вручную.
|
||||
|
||||
### Запуск сервера
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start
|
||||
```
|
||||
|
||||
Эта команда скачивает Docker-образ `twentycrm/twenty-app-dev:latest` (если его ещё нет), создаёт контейнер с именем `twenty-app-dev` и запускает его на порту **2020**. CLI ждёт, пока сервер пройдёт проверку работоспособности, прежде чем вернуть управление.
|
||||
|
||||
Создаются два тома Docker для сохранения данных между перезапусками:
|
||||
|
||||
* `twenty-app-dev-data` — база данных PostgreSQL
|
||||
* `twenty-app-dev-storage` — файловое хранилище
|
||||
|
||||
Если порт 2020 уже используется, вы можете запустить на другом порту:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server start --port 3030
|
||||
```
|
||||
|
||||
CLI автоматически настраивает внутренние `NODE_PORT` и `SERVER_URL` контейнера в соответствии с выбранным портом, чтобы логические функции, OAuth и прочие внутренние сетевые взаимодействия работали корректно.
|
||||
|
||||
После запуска сервер автоматически регистрируется как remote `local` в конфигурации вашего CLI.
|
||||
|
||||
### Проверка состояния сервера
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server status
|
||||
```
|
||||
|
||||
Показывает, запущен ли сервер, его URL и учётные данные по умолчанию (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### Просмотр журналов сервера
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs
|
||||
```
|
||||
|
||||
Выводит журналы контейнера в потоковом режиме. Используйте `--lines`, чтобы задать, сколько последних строк показывать:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server logs --lines 100
|
||||
```
|
||||
|
||||
### Остановка сервера
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server stop
|
||||
```
|
||||
|
||||
Останавливает контейнер. Ваши данные сохраняются в томах Docker — следующий `start` продолжит с того места, где вы остановились.
|
||||
|
||||
### Сброс сервера
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty server reset
|
||||
```
|
||||
|
||||
Удаляет контейнер и оба тома Docker, полностью стирая все данные. Следующий `start` создаст новый чистый экземпляр.
|
||||
|
||||
<Note>
|
||||
Для работы сервера необходимо, чтобы **Docker** был запущен. Если вы видите ошибку "Docker not running", убедитесь, что запущен Docker Desktop (или демон Docker).
|
||||
</Note>
|
||||
|
||||
### Справочник команд
|
||||
|
||||
| Команда | Описание |
|
||||
| -------------------------------------- | -------------------------------------------------------------- |
|
||||
| `yarn twenty server start` | Запустить локальный сервер (при необходимости скачивает образ) |
|
||||
| `yarn twenty server start --port 3030` | Запустить на пользовательском порту |
|
||||
| `yarn twenty server stop` | Остановить сервер (данные сохраняются) |
|
||||
| `yarn twenty server status` | Показать состояние сервера, URL и учётные данные |
|
||||
| `yarn twenty server logs` | Потоковый вывод журналов сервера |
|
||||
| `yarn twenty server logs --lines 100` | Показать последние 100 строк журнала |
|
||||
| `yarn twenty server reset` | Удалить все данные и начать с чистого листа |
|
||||
|
||||
## CI с GitHub Actions
|
||||
|
||||
Скэффолдер генерирует готовый к использованию workflow GitHub Actions в `.github/workflows/ci.yml`. Он автоматически запускает ваши интеграционные тесты при каждом пуше в `main` и в pull request'ах.
|
||||
|
||||
Рабочий процесс:
|
||||
|
||||
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` в начале workflow.
|
||||
|
||||
## Ручная настройка (без генератора)
|
||||
|
||||
Хотя мы рекомендуем использовать `create-twenty-app` для наилучшего старта, вы также можете настроить проект вручную. Не устанавливайте CLI глобально. Вместо этого добавьте `twenty-sdk` как локальную зависимость и настройте один скрипт в вашем package.json:
|
||||
Если вы предпочитаете настроить всё самостоятельно, не используя `create-twenty-app`, это можно сделать в два шага.
|
||||
|
||||
**1. Добавьте `twenty-sdk` и `twenty-client-sdk` в зависимости:**
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D twenty-sdk
|
||||
yarn add twenty-sdk twenty-client-sdk
|
||||
```
|
||||
|
||||
Затем добавьте скрипт `twenty`:
|
||||
**2. Добавьте скрипт `twenty` в ваш `package.json`:**
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
@@ -221,25 +393,19 @@ yarn add -D twenty-sdk
|
||||
}
|
||||
```
|
||||
|
||||
Теперь вы можете запускать все команды через `yarn twenty <command>`, например, `yarn twenty dev`, `yarn twenty help` и т. д.
|
||||
Теперь вы можете запускать `yarn twenty dev`, `yarn twenty help` и все остальные команды.
|
||||
|
||||
## Как использовать локальный экземпляр Twenty
|
||||
|
||||
Если у вас уже запущен локально экземпляр Twenty (например, через `npx nx start twenty-server`), вы можете подключиться к нему вместо использования Docker:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# During scaffolding — skip Docker, connect to your running instance
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding — add a remote pointing to your instance
|
||||
yarn twenty remote add --local --port 3000
|
||||
```
|
||||
<Note>
|
||||
Не устанавливайте `twenty-sdk` глобально. Всегда используйте его как локальную зависимость проекта, чтобы каждый проект мог закреплять свою версию.
|
||||
</Note>
|
||||
|
||||
## Устранение неполадок
|
||||
|
||||
* Ошибки аутентификации: выполните `yarn twenty auth:login` и убедитесь, что у вашего ключа API есть необходимые права.
|
||||
* Не удаётся подключиться к серверу: проверьте URL API и доступность сервера Twenty.
|
||||
* Типы или клиент отсутствуют/устарели: перезапустите `yarn twenty dev` — он автоматически генерирует типизированный клиент.
|
||||
* Режим разработки не синхронизируется: убедитесь, что запущен `yarn twenty dev`, и что ваша среда не игнорирует изменения.
|
||||
Если столкнётесь с проблемами:
|
||||
|
||||
Канал помощи в Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|
||||
* Перед запуском генератора с локальным экземпляром убедитесь, что **Docker запущен**.
|
||||
* Убедитесь, что используете **Node.js 24+** (`node -v` для проверки).
|
||||
* Убедитесь, что **Corepack включён** (`corepack enable`), чтобы Yarn 4 был доступен.
|
||||
* Если зависимости, похоже, повреждены, попробуйте удалить `node_modules` и снова выполнить `yarn install`.
|
||||
|
||||
Все ещё не получается? Попросите помощи на [Discord-сервере Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
|
||||
|
||||
@@ -4,15 +4,75 @@ description: Распространяйте своё приложение Twenty
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Приложения сейчас проходят альфа-тестирование. Функциональность работает, но продолжает развиваться.
|
||||
Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться.
|
||||
</Warning>
|
||||
|
||||
## Обзор
|
||||
|
||||
После того как ваше приложение [собрано и протестировано локально](/l/ru/developers/extend/apps/building), у вас есть два пути для его распространения:
|
||||
|
||||
* **Разверните tar-архив** — загрузите своё приложение напрямую на конкретный сервер Twenty для внутреннего или частного использования.
|
||||
* **Опубликовать в npm** — разместите ваше приложение в маркетплейсе Twenty, чтобы любое рабочее пространство могло его найти и установить.
|
||||
* **Отправить tarball** — разверните приложение на конкретном сервере Twenty для внутреннего использования, не делая его общедоступным.
|
||||
|
||||
Оба пути начинаются с одного и того же шага **build**.
|
||||
|
||||
## Сборка вашего приложения
|
||||
|
||||
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```
|
||||
|
||||
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
|
||||
|
||||
## Развертывание на сервер (tarball)
|
||||
|
||||
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятий или экспериментальные сборки — вы можете развернуть tarball напрямую на сервер Twenty.
|
||||
|
||||
### Требования
|
||||
|
||||
Перед развертыванием вам нужен настроенный remote, указывающий на целевой сервер. Remotes локально хранят URL сервера и учётные данные аутентификации в `~/.twenty/config.json`.
|
||||
|
||||
Добавьте remote:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --as production
|
||||
```
|
||||
|
||||
### Развертывание
|
||||
|
||||
Соберите и загрузите ваше приложение на сервер в одном шаге:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty deploy
|
||||
# To deploy to a specific remote:
|
||||
# yarn twenty deploy --remote production
|
||||
```
|
||||
|
||||
### Общий доступ к развернутому приложению
|
||||
|
||||
Приложения в формате tarball не отображаются в публичном маркетплейсе, поэтому другие рабочие пространства на том же сервере не найдут их при просмотре. Чтобы поделиться развернутым приложением:
|
||||
|
||||
1. Перейдите в **Настройки > Приложения > Регистрации** и откройте ваше приложение
|
||||
2. На вкладке **Распространение** нажмите **Копировать ссылку для общего доступа**
|
||||
3. Поделитесь этой ссылкой с пользователями в других рабочих пространствах — она ведёт их прямо на страницу установки приложения
|
||||
|
||||
Ссылка общего доступа использует базовый URL сервера (без какого-либо поддомена рабочего пространства), поэтому она работает для любого рабочего пространства на сервере.
|
||||
|
||||
<Warning>
|
||||
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
|
||||
</Warning>
|
||||
|
||||
### Управление версиями
|
||||
|
||||
Чтобы выпустить обновление:
|
||||
|
||||
1. Обновите значение поля `version` в файле `package.json`
|
||||
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
|
||||
3. Рабочие пространства, в которых установлено приложение, увидят доступное обновление в своих настройках
|
||||
|
||||
{/* TODO: add screenshot of the Upgrade button */}
|
||||
|
||||
## Публикация в npm
|
||||
|
||||
@@ -21,29 +81,69 @@ description: Распространяйте своё приложение Twenty
|
||||
### Требования
|
||||
|
||||
* Учётная запись [npm](https://www.npmjs.com)
|
||||
* Название вашего пакета **должно** использовать префикс `twenty-app-` (например, `twenty-app-postcard-sender`)
|
||||
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
|
||||
|
||||
### Шаги
|
||||
|
||||
1. **Соберите приложение** — CLI компилирует исходные файлы TypeScript и генерирует манифест приложения:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty build
|
||||
```json filename="package.json"
|
||||
{
|
||||
"name": "twenty-app-postcard-sender",
|
||||
"version": "1.0.0",
|
||||
"keywords": ["twenty-app"]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Опубликуйте в npm** — отправьте собранный пакет в реестр npm:
|
||||
### Метаданные маркетплейса
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish
|
||||
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
|
||||
|
||||
```ts src/application-config.ts
|
||||
export default defineApplication({
|
||||
universalIdentifier: '...',
|
||||
displayName: 'My App',
|
||||
description: 'A great app',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
logoUrl: 'public/logo.png',
|
||||
screenshots: [
|
||||
'public/screenshot-1.png',
|
||||
'public/screenshot-2.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Автоматическое обнаружение
|
||||
See the [defineApplication accordion](/l/ru/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
Пакеты с префиксом `twenty-app-` автоматически обнаруживаются каталогом маркетплейса Twenty. После публикации ваше приложение появится в маркетплейсе в течение нескольких минут — ручная регистрация или одобрение не требуются.
|
||||
### Publish
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish
|
||||
```
|
||||
|
||||
Чтобы опубликовать с определённым dist-tag (например, `beta` или `next`):
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty publish --tag beta
|
||||
```
|
||||
|
||||
### Как работает обнаружение приложений в маркетплейсе
|
||||
|
||||
Сервер Twenty синхронизирует каталог маркетплейса из реестра npm **каждый час**.
|
||||
|
||||
You can trigger the sync immediately instead of waiting:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty catalog-sync
|
||||
# To target a specific remote:
|
||||
# yarn twenty catalog-sync --remote production
|
||||
```
|
||||
|
||||
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
|
||||
|
||||
<Note>
|
||||
Если ваше приложение не определяет `aboutDescription` в `defineApplication()`, маркетплейс автоматически использует `README.md` вашего пакета из npm в качестве содержимого страницы «О приложении». Это означает, что вы можете поддерживать единый README как для npm, так и для маркетплейса Twenty. Если вы хотите другое описание в маркетплейсе, явно задайте `aboutDescription`.
|
||||
</Note>
|
||||
|
||||
### Публикация через CI
|
||||
|
||||
Сгенерированный шаблоном проект включает workflow GitHub Actions, который выполняет публикацию при каждом релизе. Он запускает `app:build`, затем `npm publish --provenance` из результатов сборки:
|
||||
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
|
||||
|
||||
```yaml filename=".github/workflows/publish.yml"
|
||||
name: Publish
|
||||
@@ -68,52 +168,24 @@ jobs:
|
||||
- run: npx twenty build
|
||||
- run: npm publish --provenance --access public
|
||||
working-directory: .twenty/output
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
```
|
||||
|
||||
Для других CI-систем (GitLab CI, CircleCI и др.) применимы те же три команды: `yarn install`, `npx twenty build`, затем `npm publish` из `.twenty/output`.
|
||||
|
||||
<Tip>
|
||||
**npm provenance** — опционально, но рекомендуется. Публикация с флагом `--provenance` добавляет к вашему пакету в npm значок доверия, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. См. инструкции по настройке в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements).
|
||||
</Tip>
|
||||
|
||||
## Внутреннее распространение
|
||||
|
||||
Для приложений, которые вы не хотите делать общедоступными — собственные инструменты, интеграции только для предприятия или экспериментальные сборки — вы можете отправить tarball напрямую на сервер Twenty.
|
||||
|
||||
### Отправка tarball
|
||||
|
||||
Соберите приложение и разверните его на конкретном сервере одним шагом:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx twenty publish --server <server-url>
|
||||
```
|
||||
|
||||
Любое рабочее пространство на этом сервере сможет установить и обновить приложение на странице настроек **Applications**.
|
||||
|
||||
### Управление версиями
|
||||
|
||||
Чтобы выпустить обновление:
|
||||
|
||||
1. Обновите значение поля `version` в файле `package.json`
|
||||
2. Отправьте новый tarball командой `npx twenty publish --server <server-url>`
|
||||
3. Рабочие пространства на этом сервере увидят доступное обновление в своих настройках
|
||||
Для других CI-систем (GitLab CI, CircleCI и др.) применимы те же три команды: `yarn install`, `yarn twenty build`, затем `npm publish` из `.twenty/output`.
|
||||
|
||||
<Note>
|
||||
Внутренние приложения ограничены сервером, на который они отправлены. Они не появятся в публичном маркетплейсе и не могут быть установлены рабочими пространствами на других серверах.
|
||||
**npm provenance** — опционально, но рекомендуется. Публикация с флагом `--provenance` добавляет к вашему пакету в npm значок доверия, позволяя пользователям проверить, что пакет был собран из конкретного коммита в общедоступном конвейере CI. См. инструкции по настройке в [документации по npm provenance](https://docs.npmjs.com/generating-provenance-statements).
|
||||
</Note>
|
||||
|
||||
## Категории приложений
|
||||
## Установка приложений
|
||||
|
||||
Twenty группирует приложения в три категории в зависимости от способа их распространения:
|
||||
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
|
||||
|
||||
| Категория | Как это работает | Отображается в маркетплейсе? |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| **Разработка** | Локальные приложения в режиме разработки, запущенные через `yarn twenty dev`. Используются для сборки и тестирования. | Нет |
|
||||
| **Опубликовано** | Приложения, опубликованные в npm с префиксом `twenty-app-`. Отображаются в маркетплейсе, доступные для установки любому рабочему пространству. | Да |
|
||||
| **Внутренний** | Приложения, развернутые через tarball на конкретном сервере. Доступны только рабочим пространствам на этом сервере. | Нет |
|
||||
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
|
||||
|
||||
<Tip>
|
||||
Начните в режиме **Разработка** во время создания приложения. Когда будет готово, выберите **Опубликовано** (npm) для широкого распространения или **Внутренний** (tarball) для приватного развертывания.
|
||||
</Tip>
|
||||
{/* TODO: add screenshot of the UI when the app is registered */}
|
||||
|
||||
You can also install apps from the command line:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty install
|
||||
```
|
||||
|
||||