Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0379aea0b1 | ||
|
|
f3e0c12ce6 | ||
|
|
0641e07ca6 | ||
|
|
dfd28f5b4a | ||
|
|
349bfc8462 | ||
|
|
262f9f5fe1 | ||
|
|
5f558e5539 | ||
|
|
1cb4c98cb3 | ||
|
|
a3c392ce8b | ||
|
|
ab13020e2b | ||
|
|
3f420c84d7 | ||
|
|
0ef4741473 | ||
|
|
b5db955ac8 | ||
|
|
2a6fcfcfb3 | ||
|
|
5bfa4c5c39 | ||
|
|
1685d066be | ||
|
|
6a3281a18d | ||
|
|
741e9a8f81 | ||
|
|
0897575fd0 | ||
|
|
501fcc737f | ||
|
|
c9deab4373 | ||
|
|
c1da7be6d7 | ||
|
|
c59f420d21 | ||
|
|
06d4d62e90 | ||
|
|
eb4665bc98 | ||
|
|
f19fcd0010 | ||
|
|
cb3e32df86 | ||
|
|
db5b4d9c6c | ||
|
|
660536d6bb | ||
|
|
e8f8189167 | ||
|
|
78473a606a | ||
|
|
b21fb4aa6f | ||
|
|
38664249cf | ||
|
|
69542898a1 | ||
|
|
09beddb63d | ||
|
|
2eac82c207 | ||
|
|
f262437da6 | ||
|
|
15d0970f72 | ||
|
|
1adc325887 | ||
|
|
5726bd6e17 | ||
|
|
102b49f919 | ||
|
|
2af3121c51 | ||
|
|
a024a04e01 | ||
|
|
f0c83434a7 | ||
|
|
b699619756 | ||
|
|
b2f053490d | ||
|
|
21de221420 | ||
|
|
d9b3507866 | ||
|
|
b346f4fb59 | ||
|
|
2c5af2654d | ||
|
|
6cbc7725b7 | ||
|
|
744ef3aa9d | ||
|
|
ef92d2d321 | ||
|
|
00c3cd1051 | ||
|
|
99f885306e | ||
|
|
413d1124bb | ||
|
|
ab5fb1f658 | ||
|
|
e4e7137660 | ||
|
|
7fb8cc1c39 |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"install": "curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - && sudo apt-get install -y nodejs && node --version && yarn install && echo 'Setting up Docker Compose environment...' && cd packages/twenty-docker && cp -n docker-compose.yml docker-compose.dev.yml || true && echo 'Dependencies installed and docker-compose prepared'",
|
||||
"start": "sudo service docker start && echo 'Docker service started' && cd packages/twenty-docker && echo 'Installing yq for YAML processing...' && sudo apt-get update -qq && sudo apt-get install -y wget && wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && sudo chmod +x /usr/local/bin/yq && echo 'Patching docker-compose for local development...' && yq eval 'del(.services.server.image)' -i docker-compose.dev.yml && yq eval '.services.server.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.server.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && yq eval 'del(.services.worker.image)' -i docker-compose.dev.yml && yq eval '.services.worker.build.context = \"../../\"' -i docker-compose.dev.yml && yq eval '.services.worker.build.dockerfile = \"./packages/twenty-docker/twenty/Dockerfile\"' -i docker-compose.dev.yml && echo 'Setting up .env file with database configuration...' && echo 'SERVER_URL=http://localhost:3000' > .env && echo 'APP_SECRET='$(openssl rand -base64 32) >> .env && echo 'PG_DATABASE_PASSWORD='$(openssl rand -hex 16) >> .env && echo 'PG_DATABASE_URL=postgres://postgres:password@localhost:5432/postgres' >> .env && echo 'SIGN_IN_PREFILLED=true' >> .env && echo 'Building and starting services...' && docker-compose -f docker-compose.dev.yml up -d --build && echo 'Waiting for services to initialize...' && sleep 30 && echo 'Checking service health...' && docker-compose -f docker-compose.dev.yml ps && echo 'Environment setup complete!'",
|
||||
"terminals": [
|
||||
{
|
||||
"name": "Database Setup & Seed",
|
||||
"command": "sleep 40 && cd packages/twenty-docker && echo 'Waiting for PostgreSQL to be ready...' && until docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres; do echo 'Waiting for PostgreSQL...'; sleep 5; done && echo 'PostgreSQL is ready!' && echo 'Waiting for Twenty server to be healthy...' && until docker-compose -f docker-compose.dev.yml exec -T server curl --fail http://localhost:3000/healthz 2>/dev/null; do echo 'Waiting for server...'; sleep 5; done && echo 'Server is healthy!' && echo 'Running database setup and seeding...' && docker-compose -f docker-compose.dev.yml exec -T server npx nx database:reset twenty-server && echo 'Database seeded successfully!' && bash"
|
||||
},
|
||||
{
|
||||
"name": "Application Logs",
|
||||
"command": "sleep 35 && cd packages/twenty-docker && echo 'Following application logs...' && docker-compose -f docker-compose.dev.yml logs -f server worker"
|
||||
},
|
||||
{
|
||||
"name": "Service Monitor",
|
||||
"command": "sleep 15 && cd packages/twenty-docker && echo '=== Service Status Monitor ===' && while true; do clear; echo '=== Service Status at $(date) ===' && docker-compose -f docker-compose.dev.yml ps && echo '\\n=== Health Status ===' && (docker-compose -f docker-compose.dev.yml exec -T server curl -s http://localhost:3000/healthz 2>/dev/null && echo '✅ Twenty Server: Healthy') || echo '❌ Twenty Server: Not Ready' && (docker-compose -f docker-compose.dev.yml exec -T db pg_isready -U postgres 2>/dev/null && echo '✅ PostgreSQL: Ready') || echo '❌ PostgreSQL: Not Ready' && echo '\\n=== Database Connection Test ===' && docker-compose -f docker-compose.dev.yml exec -T server node -e \"const { Client } = require('pg'); const client = new Client({connectionString: process.env.PG_DATABASE_URL}); client.connect().then(() => {console.log('✅ Database Connection: OK'); client.end();}).catch(e => console.log('❌ Database Connection: Failed -', e.message));\" || echo 'Connection test failed' && sleep 45; done"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"install": "yarn install",
|
||||
"start": "sudo service docker start && sleep 2 && (docker start twenty_pg 2>/dev/null || make -C packages/twenty-docker postgres-on-docker) && (docker start twenty_redis 2>/dev/null || make -C packages/twenty-docker redis-on-docker) && until docker exec twenty_pg pg_isready -U postgres -h localhost 2>/dev/null; do sleep 1; done && echo 'PostgreSQL ready' && until docker exec twenty_redis redis-cli ping 2>/dev/null | grep -q PONG; do sleep 1; done && echo 'Redis ready' && bash packages/twenty-utils/setup-dev-env.sh && npx nx database:reset twenty-server",
|
||||
"start": "(sudo service docker start || service docker start || true) && bash packages/twenty-utils/setup-dev-env.sh && npx nx database:reset twenty-server",
|
||||
"terminals": [
|
||||
{
|
||||
"name": "Development Server",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
storage: /tmp/verdaccio-storage
|
||||
auth:
|
||||
htpasswd:
|
||||
file: /tmp/verdaccio-htpasswd
|
||||
max_users: 100
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.npmjs.org/
|
||||
packages:
|
||||
'twenty-sdk':
|
||||
access: $all
|
||||
publish: $all
|
||||
'create-twenty-app':
|
||||
access: $all
|
||||
publish: $all
|
||||
'**':
|
||||
access: $all
|
||||
proxy: npmjs
|
||||
log: { type: stdout, format: pretty, level: warn }
|
||||
@@ -0,0 +1,182 @@
|
||||
name: CI Create App E2E
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
packages/create-twenty-app/**
|
||||
packages/twenty-sdk/**
|
||||
packages/twenty-shared/**
|
||||
packages/twenty-server/**
|
||||
!packages/create-twenty-app/package.json
|
||||
!packages/twenty-sdk/package.json
|
||||
!packages/twenty-shared/package.json
|
||||
!packages/twenty-server/package.json
|
||||
create-app-e2e:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
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: Set CI version and prepare packages for publish
|
||||
run: |
|
||||
CI_VERSION="0.0.0-ci.$(date +%s)"
|
||||
echo "CI_VERSION=$CI_VERSION" >> $GITHUB_ENV
|
||||
npx nx run-many -t set-local-version -p twenty-sdk create-twenty-app --releaseVersion=$CI_VERSION
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
npx nx build twenty-sdk
|
||||
npx nx build create-twenty-app
|
||||
|
||||
- name: Install and start Verdaccio
|
||||
run: |
|
||||
npx verdaccio --config .github/verdaccio-config.yaml &
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
if curl -s http://localhost:4873 > /dev/null 2>&1; then
|
||||
echo "Verdaccio is ready"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for Verdaccio... ($i/30)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Publish packages to local registry
|
||||
run: |
|
||||
npm set //localhost:4873/:_authToken "ci-auth-token"
|
||||
|
||||
for pkg in twenty-sdk create-twenty-app; do
|
||||
cd packages/$pkg
|
||||
npm publish --registry http://localhost:4873 --tag ci
|
||||
cd ../..
|
||||
done
|
||||
|
||||
- name: Scaffold app using published create-twenty-app
|
||||
run: |
|
||||
npm install -g create-twenty-app@$CI_VERSION --registry http://localhost:4873
|
||||
create-twenty-app --version
|
||||
mkdir -p /tmp/e2e-test-workspace
|
||||
cd /tmp/e2e-test-workspace
|
||||
create-twenty-app test-app --exhaustive --display-name "Test App" --description "E2E test app"
|
||||
|
||||
- name: Install scaffolded app dependencies
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
echo 'npmRegistryServer: "http://localhost:4873"' >> .yarnrc.yml
|
||||
echo 'unsafeHttpWhitelist: ["localhost"]' >> .yarnrc.yml
|
||||
YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install --no-immutable
|
||||
|
||||
- name: Verify installed app versions
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
echo "--- Checking package.json references correct SDK version ---"
|
||||
node -e "
|
||||
const pkg = require('./package.json');
|
||||
const sdkVersion = pkg.devDependencies['twenty-sdk'];
|
||||
if (!sdkVersion.startsWith('0.0.0-ci.')) {
|
||||
console.error('Expected twenty-sdk version to start with 0.0.0-ci., got:', sdkVersion);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('SDK version in scaffolded app:', sdkVersion);
|
||||
"
|
||||
|
||||
- name: Verify SDK CLI is available
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty --version
|
||||
|
||||
- name: Setup server environment
|
||||
run: npx nx reset:env:e2e-testing-server twenty-server
|
||||
|
||||
- name: Build server
|
||||
run: npx nx build twenty-server
|
||||
|
||||
- name: Create and setup database
|
||||
run: |
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
npx nx run twenty-server:database:reset
|
||||
|
||||
- name: Start server
|
||||
run: |
|
||||
npx nx start twenty-server &
|
||||
echo "Waiting for server to be ready..."
|
||||
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
|
||||
|
||||
- name: Authenticate with twenty-server
|
||||
env:
|
||||
SEED_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik'
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty auth:login --api-key $SEED_API_KEY --api-url http://localhost:3000
|
||||
|
||||
- name: Build scaffolded app
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty app:build
|
||||
test -d .twenty/output
|
||||
|
||||
- name: Execute hello-world logic function
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
EXEC_OUTPUT=$(npx --no-install twenty function:execute --functionName hello-world-logic-function)
|
||||
echo "$EXEC_OUTPUT"
|
||||
echo "$EXEC_OUTPUT" | grep -q "Hello, World!"
|
||||
|
||||
- name: Run scaffolded app integration test
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
yarn test
|
||||
|
||||
ci-create-app-e2e-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, create-app-e2e]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
@@ -188,13 +188,22 @@ IMPORTANT: Use Context7 for code generation, setup or configuration steps, or li
|
||||
- Descriptive test names: "should [behavior] when [condition]"
|
||||
- Clear mocks between tests with `jest.clearAllMocks()`
|
||||
|
||||
## CI Environment (GitHub Actions)
|
||||
## Dev Environment Setup
|
||||
|
||||
When running in CI, the dev environment is **not** pre-configured. Dependencies are installed but builds, env files, and databases are not set up.
|
||||
All dev environments (Claude Code web, Cursor, local) use one script:
|
||||
|
||||
- **Before running tests, builds, lint, type checks, or DB operations**, run: `bash packages/twenty-utils/setup-dev-env.sh`
|
||||
```bash
|
||||
bash packages/twenty-utils/setup-dev-env.sh
|
||||
```
|
||||
|
||||
This handles everything: starts Postgres + Redis (auto-detects local services vs Docker), creates databases, and copies `.env` files. Idempotent — safe to run multiple times.
|
||||
|
||||
- `--docker` — force Docker mode (uses `packages/twenty-docker/docker-compose.dev.yml`)
|
||||
- `--down` — stop services
|
||||
- `--reset` — wipe data and restart fresh
|
||||
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
|
||||
- The script is idempotent and safe to run multiple times.
|
||||
|
||||
**Note:** CI workflows (GitHub Actions) manage services via Actions service containers and run setup steps individually — they don't use this script.
|
||||
|
||||
## Important Files
|
||||
- `nx.json` - Nx workspace configuration with task definitions
|
||||
|
||||
@@ -136,6 +136,14 @@
|
||||
"cache": true,
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"set-local-version": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": false,
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"command": "npm pkg set version={args.releaseVersion}"
|
||||
}
|
||||
},
|
||||
"storybook:build": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": true,
|
||||
|
||||
+1
-1
@@ -164,6 +164,7 @@
|
||||
"tsc-alias": "^1.8.16",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.17.0",
|
||||
"verdaccio": "^6.3.1",
|
||||
"vite": "^7.0.0",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
@@ -206,7 +207,6 @@
|
||||
"packages/twenty-e2e-testing",
|
||||
"packages/twenty-shared",
|
||||
"packages/twenty-sdk",
|
||||
"packages/twenty-standard-application",
|
||||
"packages/twenty-apps",
|
||||
"packages/twenty-cli",
|
||||
"packages/create-twenty-app",
|
||||
|
||||
@@ -58,6 +58,12 @@ yarn twenty function:execute --preInstall
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
# Build the app for distribution
|
||||
yarn twenty app:build
|
||||
|
||||
# Publish the app to npm or directly to a Twenty server
|
||||
yarn twenty app:publish
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty app:uninstall
|
||||
```
|
||||
@@ -109,29 +115,40 @@ npx create-twenty-app@latest my-app -m
|
||||
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
|
||||
- `CoreApiClient` (for workspace data via `/graphql`) is auto-generated by `yarn twenty app:dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/clients'`.
|
||||
|
||||
## Publish your application
|
||||
## Build and publish your application
|
||||
|
||||
Applications are currently stored in `twenty/packages/twenty-apps`.
|
||||
Once your app is ready, build and publish it using the CLI:
|
||||
|
||||
You can share your application with all Twenty users:
|
||||
```bash
|
||||
# Build the app (output goes to .twenty/output/)
|
||||
yarn twenty app:build
|
||||
|
||||
# Build and create a tarball (.tgz) for distribution
|
||||
yarn twenty app:build --tarball
|
||||
|
||||
# Publish to npm (requires npm login)
|
||||
yarn twenty app:publish
|
||||
|
||||
# Publish with a dist-tag (e.g. beta, next)
|
||||
yarn twenty app:publish --tag beta
|
||||
|
||||
# Publish directly to a Twenty server (builds, uploads, and installs in one step)
|
||||
yarn twenty app:publish --server https://app.twenty.com
|
||||
```
|
||||
|
||||
### Publish to the Twenty marketplace
|
||||
|
||||
You can also contribute your application to the curated marketplace:
|
||||
|
||||
```bash
|
||||
# pull the Twenty project
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
cd twenty
|
||||
|
||||
# create a new branch
|
||||
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
|
||||
|
||||
```bash
|
||||
git commit -m "Add new application"
|
||||
git push
|
||||
```
|
||||
|
||||
Our team reviews contributions for quality, security, and reusability before merging.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.7.0-canary.0",
|
||||
"version": "0.7.0",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"command": "node dist/cli.cjs"
|
||||
}
|
||||
},
|
||||
"set-local-version": {},
|
||||
"typecheck": {},
|
||||
"lint": {},
|
||||
"test": {
|
||||
|
||||
@@ -18,6 +18,15 @@ const program = new Command(packageJson.name)
|
||||
'-m, --minimal',
|
||||
'Create only core entities (application-config and default-role)',
|
||||
)
|
||||
.option('-n, --name <name>', 'Application name (skips prompt)')
|
||||
.option(
|
||||
'-d, --display-name <displayName>',
|
||||
'Application display name (skips prompt)',
|
||||
)
|
||||
.option(
|
||||
'--description <description>',
|
||||
'Application description (skips prompt)',
|
||||
)
|
||||
.helpOption('-h, --help', 'Display this help message.')
|
||||
.action(
|
||||
async (
|
||||
@@ -25,6 +34,9 @@ const program = new Command(packageJson.name)
|
||||
options?: {
|
||||
exhaustive?: boolean;
|
||||
minimal?: boolean;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
},
|
||||
) => {
|
||||
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
|
||||
@@ -47,9 +59,20 @@ const program = new Command(packageJson.name)
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options?.name !== undefined && options.name.trim().length === 0) {
|
||||
console.error(chalk.red('Error: --name cannot be empty.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
|
||||
|
||||
await new CreateAppCommand().execute(directory, mode);
|
||||
await new CreateAppCommand().execute({
|
||||
directory,
|
||||
mode,
|
||||
name: options?.name,
|
||||
displayName: options?.displayName,
|
||||
description: options?.description,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as fs from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import * as path from 'path';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
type ExampleOptions,
|
||||
@@ -15,16 +16,23 @@ import {
|
||||
|
||||
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
|
||||
|
||||
type CreateAppOptions = {
|
||||
directory?: string;
|
||||
mode?: ScaffoldingMode;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export class CreateAppCommand {
|
||||
async execute(
|
||||
directory?: string,
|
||||
mode: ScaffoldingMode = 'exhaustive',
|
||||
): Promise<void> {
|
||||
async execute(options: CreateAppOptions = {}): Promise<void> {
|
||||
try {
|
||||
const { appName, appDisplayName, appDirectory, appDescription } =
|
||||
await this.getAppInfos(directory);
|
||||
await this.getAppInfos(options);
|
||||
|
||||
const exampleOptions = this.resolveExampleOptions(mode);
|
||||
const exampleOptions = this.resolveExampleOptions(
|
||||
options.mode ?? 'exhaustive',
|
||||
);
|
||||
|
||||
await this.validateDirectory(appDirectory);
|
||||
|
||||
@@ -54,19 +62,25 @@ export class CreateAppCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async getAppInfos(directory?: string): Promise<{
|
||||
private async getAppInfos(options: CreateAppOptions): Promise<{
|
||||
appName: string;
|
||||
appDisplayName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}> {
|
||||
const { directory } = options;
|
||||
|
||||
const hasName = isDefined(options.name) || isDefined(directory);
|
||||
const hasDisplayName = isDefined(options.displayName);
|
||||
const hasDescription = isDefined(options.description);
|
||||
|
||||
const { name, displayName, description } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Application name:',
|
||||
when: () => !directory,
|
||||
default: 'my-awesome-app',
|
||||
when: () => !hasName,
|
||||
default: 'my-twenty-app',
|
||||
validate: (input) => {
|
||||
if (input.length === 0) return 'Application name is required';
|
||||
return true;
|
||||
@@ -76,25 +90,33 @@ export class CreateAppCommand {
|
||||
type: 'input',
|
||||
name: 'displayName',
|
||||
message: 'Application display name:',
|
||||
default: (answers: any) => {
|
||||
return convertToLabel(answers?.name ?? directory);
|
||||
when: () => !hasDisplayName,
|
||||
default: (answers: { name?: string }) => {
|
||||
return convertToLabel(
|
||||
answers?.name ?? options.name ?? directory ?? '',
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'description',
|
||||
message: 'Application description (optional):',
|
||||
when: () => !hasDescription,
|
||||
default: '',
|
||||
},
|
||||
]);
|
||||
|
||||
const computedName = name ?? directory;
|
||||
const appName = (
|
||||
options.name ??
|
||||
name ??
|
||||
directory ??
|
||||
'my-twenty-app'
|
||||
).trim();
|
||||
|
||||
const appName = computedName.trim();
|
||||
const appDisplayName =
|
||||
(options.displayName ?? displayName)?.trim() || convertToLabel(appName);
|
||||
|
||||
const appDisplayName = displayName.trim();
|
||||
|
||||
const appDescription = description.trim();
|
||||
const appDescription = (options.description ?? description ?? '').trim();
|
||||
|
||||
const appDirectory = directory
|
||||
? path.join(CURRENT_EXECUTION_DIRECTORY, directory)
|
||||
|
||||
@@ -30,12 +30,12 @@ export const copyBaseApplicationProject = async ({
|
||||
includeExampleIntegrationTest: exampleOptions.includeExampleIntegrationTest,
|
||||
});
|
||||
|
||||
await createYarnLock(appDirectory);
|
||||
|
||||
await createGitignore(appDirectory);
|
||||
|
||||
await createPublicAssetDirectory(appDirectory);
|
||||
|
||||
await createYarnLock(appDirectory);
|
||||
|
||||
const sourceFolderPath = join(appDirectory, SRC_FOLDER);
|
||||
|
||||
await fs.ensureDir(sourceFolderPath);
|
||||
@@ -142,13 +142,6 @@ const createPublicAssetDirectory = async (appDirectory: string) => {
|
||||
await fs.ensureDir(join(appDirectory, ASSETS_DIR));
|
||||
};
|
||||
|
||||
const createYarnLock = async (appDirectory: string) => {
|
||||
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
|
||||
};
|
||||
const createGitignore = async (appDirectory: string) => {
|
||||
const gitignoreContent = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
@@ -590,6 +583,14 @@ export default defineApplication({
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createYarnLock = async (appDirectory: string) => {
|
||||
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
|
||||
};
|
||||
|
||||
const createPackageJson = async ({
|
||||
appName,
|
||||
appDirectory,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Development infrastructure services only (Postgres + Redis).
|
||||
# Use this when developing locally against the source code.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.dev.yml up -d
|
||||
# docker compose -f docker-compose.dev.yml down # stop
|
||||
# docker compose -f docker-compose.dev.yml down -v # stop + wipe data
|
||||
|
||||
name: twenty-dev
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16
|
||||
volumes:
|
||||
- dev-db-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: default
|
||||
healthcheck:
|
||||
test: pg_isready -U postgres -h localhost -d postgres
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: ["--maxmemory-policy", "noeviction"]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
dev-db-data:
|
||||
@@ -15,7 +15,6 @@ 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-sdk/package.json /app/packages/twenty-sdk/
|
||||
COPY ./packages/twenty-standard-application/package.json /app/packages/twenty-standard-application/
|
||||
|
||||
# Install all dependencies
|
||||
RUN yarn && yarn cache clean && npx nx reset
|
||||
@@ -29,13 +28,11 @@ COPY ./packages/twenty-emails /app/packages/twenty-emails
|
||||
COPY ./packages/twenty-shared /app/packages/twenty-shared
|
||||
COPY ./packages/twenty-ui /app/packages/twenty-ui
|
||||
COPY ./packages/twenty-sdk /app/packages/twenty-sdk
|
||||
COPY ./packages/twenty-standard-application /app/packages/twenty-standard-application
|
||||
COPY ./packages/twenty-server /app/packages/twenty-server
|
||||
|
||||
RUN npx nx build twenty-standard-application
|
||||
RUN npx nx run twenty-server:build
|
||||
|
||||
RUN yarn workspaces focus --production twenty-emails twenty-shared twenty-sdk twenty-standard-application twenty-server
|
||||
RUN yarn workspaces focus --production twenty-emails twenty-shared twenty-sdk twenty-server
|
||||
|
||||
# Build the front
|
||||
FROM common-deps AS twenty-front-build
|
||||
|
||||
@@ -63,6 +63,12 @@ yarn twenty function:execute --preInstall
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
# Build the app for distribution
|
||||
yarn twenty app:build
|
||||
|
||||
# Publish the app to npm or a Twenty server
|
||||
yarn twenty app:publish
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty app:uninstall
|
||||
|
||||
@@ -1224,6 +1230,113 @@ Key points:
|
||||
|
||||
Explore a minimal, end-to-end example that demonstrates objects, logic functions, front components, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
## Building your app
|
||||
|
||||
Once you've developed your app with `app:dev`, use `app:build` to compile it into a distributable package.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Build the app (output goes to .twenty/output/)
|
||||
yarn twenty app:build
|
||||
|
||||
# Build and create a tarball (.tgz) for distribution
|
||||
yarn twenty app:build --tarball
|
||||
```
|
||||
|
||||
The build process:
|
||||
|
||||
1. **Parses and validates the manifest** — reads all `defineX()` entities from your source files and validates the manifest structure.
|
||||
2. **Compiles logic functions and front components** — bundles TypeScript sources into ESM `.mjs` files using esbuild.
|
||||
3. **Generates checksums** — computes MD5 hashes for each built file, stored in the manifest as `builtHandlerChecksum` / `builtComponentChecksum`.
|
||||
4. **Generates the typed API client** — introspects the GraphQL schema and generates typed `CoreApiClient` and `MetadataApiClient` clients.
|
||||
5. **Runs a TypeScript type check** — runs `tsc --noEmit` to catch type errors before publishing.
|
||||
6. **Rebuilds with the generated client** — performs a second compilation pass so the generated client types are included.
|
||||
7. **Optionally creates a tarball** — if `--tarball` is passed, runs `npm pack` to create a `.tgz` file ready for distribution.
|
||||
|
||||
The build output in `.twenty/output/` contains:
|
||||
|
||||
```text
|
||||
.twenty/output/
|
||||
├── manifest.json # Manifest with checksums for all built files
|
||||
├── package.json # Copied from app root
|
||||
├── yarn.lock # Copied from app root
|
||||
├── src/
|
||||
│ ├── logic-functions/ # Compiled .mjs logic function files
|
||||
│ └── front-components/ # Compiled .mjs front component files
|
||||
├── public/ # Static assets (if any)
|
||||
└── my-app-1.0.0.tgz # Only with --tarball flag
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `[appPath]` | Path to the app directory (defaults to current directory) |
|
||||
| `--tarball` | Also pack the output into a `.tgz` tarball |
|
||||
|
||||
## Publishing your app
|
||||
|
||||
Use `app:publish` to distribute your app — either to the npm registry or directly to a Twenty server.
|
||||
|
||||
### Publish to npm (default)
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Publish to npm (requires npm login)
|
||||
yarn twenty app:publish
|
||||
|
||||
# Publish with a dist-tag (e.g. beta, next)
|
||||
yarn twenty app:publish --tag beta
|
||||
```
|
||||
|
||||
This builds the app and runs `npm publish` from the `.twenty/output/` directory. The published package can then be installed from the Twenty marketplace by any workspace.
|
||||
|
||||
### Publish to a Twenty server
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Publish directly to a Twenty server
|
||||
yarn twenty app:publish --server https://app.twenty.com
|
||||
```
|
||||
|
||||
This builds the app with a tarball, uploads it to the server via the `uploadAppTarball` GraphQL mutation, and triggers installation in one step. This is useful for private deployments or testing against a specific server.
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `[appPath]` | Path to the app directory (defaults to current directory) |
|
||||
| `--server <url>` | Publish to a Twenty server instead of npm |
|
||||
| `--token <token>` | Authentication token for the target server |
|
||||
| `--tag <tag>` | npm dist-tag (e.g. `beta`, `next`) — only for npm publish |
|
||||
|
||||
## Application registration
|
||||
|
||||
Before an app can be installed in a workspace, it must be **registered**. A registration is a metadata record that describes where the app comes from and how to authenticate it. This is handled automatically by the CLI in most cases.
|
||||
|
||||
### Source types
|
||||
|
||||
Each registration has a **source type** that determines how the app's files are resolved during installation:
|
||||
|
||||
| Source type | How files are resolved | Typical use case |
|
||||
|-------------|----------------------|------------------|
|
||||
| `LOCAL` | Files are synced in real-time by the CLI watcher — installation is skipped | Development with `app:dev` |
|
||||
| `NPM` | Fetched from the npm registry via the `sourcePackage` field | Published apps on npm |
|
||||
| `TARBALL` | Extracted from an uploaded `.tgz` file stored on the server | Private apps published with `--server` |
|
||||
|
||||
### How registration happens
|
||||
|
||||
- **`app:dev`** — automatically creates a `LOCAL` registration the first time you run dev mode against a workspace.
|
||||
- **`app:publish --server`** — uploads a tarball and creates (or updates) a `TARBALL` registration, then installs the app.
|
||||
- **npm marketplace** — `NPM` registrations are created when apps are synced from the npm registry into the Twenty marketplace catalog.
|
||||
- **GraphQL API** — you can also create registrations programmatically via the `createApplicationRegistration` mutation.
|
||||
|
||||
### Registration vs installation
|
||||
|
||||
**Registration** and **installation** are separate concepts:
|
||||
|
||||
- A **registration** (`ApplicationRegistration`) is a global metadata record describing the app: its name, source type, OAuth credentials, and marketplace listing status. It exists independently of any workspace.
|
||||
- An **installation** (`Application`) is a per-workspace instance. When a user installs an app, Twenty resolves the package from the registration's source, writes the built files to storage, and synchronizes the manifest (creating objects, fields, logic functions, etc.) in that workspace.
|
||||
|
||||
One registration can be installed in many workspaces. Each workspace gets its own copy of the app's files and data model.
|
||||
|
||||
### OAuth credentials
|
||||
|
||||
Each registration includes OAuth credentials (`oAuthClientId` and `oAuthClientSecret`) generated at creation time. These are used by the app to authenticate API requests on behalf of users. The client secret is returned **once** at creation — store it securely. You can rotate it later via the `rotateApplicationRegistrationClientSecret` mutation.
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -289,43 +289,57 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
|
||||
</Warning>
|
||||
|
||||
## Logic Functions
|
||||
## Logic Functions & Code Interpreter
|
||||
|
||||
Twenty supports logic functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
|
||||
Twenty supports logic functions for workflows and the code interpreter for AI data analysis. Both run user-provided code and require explicit configuration for security.
|
||||
|
||||
### Security Defaults
|
||||
|
||||
**In production (NODE_ENV=production):** Both logic functions and code interpreter default to **Disabled**. You must explicitly enable them with `LOGIC_FUNCTION_TYPE` and `CODE_INTERPRETER_TYPE` if you need these features.
|
||||
|
||||
**In development (NODE_ENV=development):** Both default to **LOCAL** for convenience when running locally.
|
||||
|
||||
<Warning>
|
||||
**Security Notice:** The local driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
|
||||
**Security Notice:** The local driver (`LOGIC_FUNCTION_TYPE=LOCAL` or `CODE_INTERPRETER_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, use `LOGIC_FUNCTION_TYPE=LAMBDA` or `CODE_INTERPRETER_TYPE=E2B` (with sandboxing), or keep them disabled.
|
||||
</Warning>
|
||||
|
||||
### Available Drivers
|
||||
### Logic Functions - Available Drivers
|
||||
|
||||
| Driver | Environment Variable | Use Case | Security Level |
|
||||
|--------|---------------------|----------|----------------|
|
||||
| Disabled | `SERVERLESS_TYPE=DISABLED` | Disable logic functions entirely | N/A |
|
||||
| Local | `SERVERLESS_TYPE=LOCAL` | Development and trusted environments | Low (no sandboxing) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Production with untrusted code | High (hardware-level isolation) |
|
||||
| Disabled | `LOGIC_FUNCTION_TYPE=DISABLED` | Disable logic functions entirely | N/A |
|
||||
| Local | `LOGIC_FUNCTION_TYPE=LOCAL` | Development and trusted environments | Low (no sandboxing) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Production with untrusted code | High (hardware-level isolation) |
|
||||
|
||||
### Recommended Configuration
|
||||
### Logic Functions - Recommended Configuration
|
||||
|
||||
**For development:**
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**For production (AWS):**
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**To disable logic functions:**
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### Code Interpreter - Available Drivers
|
||||
|
||||
| Driver | Environment Variable | Use Case | Security Level |
|
||||
|--------|---------------------|----------|----------------|
|
||||
| Disabled | `CODE_INTERPRETER_TYPE=DISABLED` | Disable AI code execution | N/A |
|
||||
| Local | `CODE_INTERPRETER_TYPE=LOCAL` | Development only | Low (no sandboxing) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Production with sandboxed execution | High (isolated sandbox) |
|
||||
|
||||
<Note>
|
||||
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a logic function will return an error. This is useful if you want to run Twenty without logic function capabilities.
|
||||
When using `LOGIC_FUNCTION_TYPE=DISABLED` or `CODE_INTERPRETER_TYPE=DISABLED`, any attempt to execute will return an error. This is useful if you want to run Twenty without these capabilities.
|
||||
</Note>
|
||||
|
||||
@@ -297,46 +297,60 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
|
||||
</Warning>
|
||||
|
||||
## الوظائف المنطقية
|
||||
## الوظائف المنطقية ومفسر الشيفرة
|
||||
|
||||
تدعم Twenty الوظائف المنطقية لعمليات سير العمل والمنطق المخصص. يتم تكوين بيئة التنفيذ عبر متغير البيئة `SERVERLESS_TYPE`.
|
||||
تدعم Twenty الوظائف المنطقية لعمليات سير العمل ومفسر الشيفرة لتحليل بيانات الذكاء الاصطناعي. كلاهما يقوم بتشغيل الشيفرة المقدمة من المستخدم ويتطلب تهيئة صريحة لأغراض الأمان.
|
||||
|
||||
### الإعدادات الافتراضية للأمان
|
||||
|
||||
**في بيئة الإنتاج (NODE_ENV=production):** يكون الإعداد الافتراضي لكل من الوظائف المنطقية ومفسر الشيفرة هو **معطل**. يجب تمكينهما صراحة باستخدام `LOGIC_FUNCTION_TYPE` و`CODE_INTERPRETER_TYPE` إذا كنت تحتاج إلى هذه الميزات.
|
||||
|
||||
**في بيئة التطوير (NODE_ENV=development):** يكون الإعداد الافتراضي لكليهما **LOCAL** لتسهيل التشغيل محلياً.
|
||||
|
||||
<Warning>
|
||||
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي (`SERVERLESS_TYPE=LOCAL`) بتشغيل الشيفرة مباشرةً على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. بالنسبة لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوق بها، نوصي بشدة باستخدام `SERVERLESS_TYPE=LAMBDA` أو `SERVERLESS_TYPE=DISABLED`.
|
||||
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي (`LOGIC_FUNCTION_TYPE=LOCAL` أو `CODE_INTERPRETER_TYPE=LOCAL`) بتشغيل الشيفرة مباشرة على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوقة، استخدم `LOGIC_FUNCTION_TYPE=LAMBDA` أو `CODE_INTERPRETER_TYPE=E2B` (مع وضع الحماية)، أو اتركهما مُعطَّلَيْن.
|
||||
</Warning>
|
||||
|
||||
### برامج التشغيل المتاحة
|
||||
### الوظائف المنطقية - برامج التشغيل المتاحة
|
||||
|
||||
| برنامج التشغيل | متغير البيئة | حالة الاستخدام | مستوى الأمان |
|
||||
| -------------- | -------------------------- | ------------------------------- | ----------------------------- |
|
||||
| معطل | `SERVERLESS_TYPE=DISABLED` | تعطيل الوظائف المنطقية بالكامل | غير متاح |
|
||||
| محلي | `SERVERLESS_TYPE=LOCAL` | بيئات التطوير والبيئات الموثوقة | منخفض (من دون عزل) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | الإنتاج مع شيفرة غير موثوق بها | مرتفع (عزل على مستوى الأجهزة) |
|
||||
| برنامج التشغيل | متغير البيئة | حالة الاستخدام | مستوى الأمان |
|
||||
| -------------- | ------------------------------ | ------------------------------- | ----------------------------- |
|
||||
| معطل | `LOGIC_FUNCTION_TYPE=DISABLED` | تعطيل الوظائف المنطقية بالكامل | غير متاح |
|
||||
| محلي | `LOGIC_FUNCTION_TYPE=LOCAL` | بيئات التطوير والبيئات الموثوقة | منخفض (من دون عزل) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | الإنتاج مع شيفرة غير موثوق بها | مرتفع (عزل على مستوى الأجهزة) |
|
||||
|
||||
### التكوين الموصى به
|
||||
### الوظائف المنطقية - الإعداد الموصى به
|
||||
|
||||
**للتطوير:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**للإنتاج (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**لتعطيل الوظائف المنطقية:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### مفسر الشيفرة - برامج التشغيل المتاحة
|
||||
|
||||
| برنامج التشغيل | متغير البيئة | حالة الاستخدام | مستوى الأمان |
|
||||
| -------------- | -------------------------------- | ------------------------------------- | ------------------------ |
|
||||
| معطل | `CODE_INTERPRETER_TYPE=DISABLED` | تعطيل تنفيذ الشيفرة بالذكاء الاصطناعي | غير متاح |
|
||||
| محلي | `CODE_INTERPRETER_TYPE=LOCAL` | للتطوير فقط | منخفض (من دون عزل) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | الإنتاج مع تنفيذ ضمن صندوق رمل معزول | مرتفعة (صندوق رمل معزول) |
|
||||
|
||||
<Note>
|
||||
عند استخدام `SERVERLESS_TYPE=DISABLED`، ستؤدي أي محاولة لتنفيذ وظيفة منطقية إلى إرجاع خطأ. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون إمكانات الوظائف المنطقية.
|
||||
عند استخدام `LOGIC_FUNCTION_TYPE=DISABLED` أو `CODE_INTERPRETER_TYPE=DISABLED`، سترجع أي محاولة للتنفيذ خطأً. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون هذه الإمكانات.
|
||||
</Note>
|
||||
|
||||
@@ -297,46 +297,60 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Nur-Umgebungsmodus:** Wenn Sie `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` setzen, fügen Sie diese Variablen stattdessen Ihrer `.env`-Datei hinzu.
|
||||
</Warning>
|
||||
|
||||
## Logikfunktionen
|
||||
## Logikfunktionen & Code-Interpreter
|
||||
|
||||
Twenty unterstützt Logikfunktionen für Workflows und benutzerdefinierte Logik. Die Ausführungsumgebung wird über die Umgebungsvariable `SERVERLESS_TYPE` konfiguriert.
|
||||
Twenty unterstützt Logikfunktionen für Workflows und den Code-Interpreter für KI-Datenanalyse. Beide führen vom Benutzer bereitgestellten Code aus und erfordern aus Sicherheitsgründen eine explizite Konfiguration.
|
||||
|
||||
### Sicherheits-Standardeinstellungen
|
||||
|
||||
**In Produktion (NODE_ENV=production):** Sowohl Logikfunktionen als auch der Code-Interpreter sind standardmäßig **deaktiviert**. Sie müssen sie, wenn Sie diese Funktionen benötigen, explizit mit `LOGIC_FUNCTION_TYPE` und `CODE_INTERPRETER_TYPE` aktivieren.
|
||||
|
||||
**In der Entwicklung (NODE_ENV=development):** Beide sind der Einfachheit halber beim lokalen Betrieb standardmäßig **LOCAL**.
|
||||
|
||||
<Warning>
|
||||
**Sicherheitshinweis:** Der lokale Treiber (`SERVERLESS_TYPE=LOCAL`) führt Code ohne Sandbox direkt auf dem Host in einem Node.js-Prozess aus. Er sollte nur für vertrauenswürdigen Code in der Entwicklung verwendet werden. Für Produktivbereitstellungen, die nicht vertrauenswürdigen Code verarbeiten, empfehlen wir nachdrücklich, `SERVERLESS_TYPE=LAMBDA` oder `SERVERLESS_TYPE=DISABLED` zu verwenden.
|
||||
**Sicherheitshinweis:** Der lokale Treiber (`LOGIC_FUNCTION_TYPE=LOCAL` oder `CODE_INTERPRETER_TYPE=LOCAL`) führt Code ohne Sandbox direkt auf dem Host in einem Node.js-Prozess aus. Er sollte nur für vertrauenswürdigen Code in der Entwicklung verwendet werden. Für Produktionsbereitstellungen, die nicht vertrauenswürdigen Code verarbeiten, verwenden Sie `LOGIC_FUNCTION_TYPE=LAMBDA` oder `CODE_INTERPRETER_TYPE=E2B` (mit Sandbox-Isolierung), oder lassen Sie sie deaktiviert.
|
||||
</Warning>
|
||||
|
||||
### Verfügbare Treiber
|
||||
### Logikfunktionen - Verfügbare Treiber
|
||||
|
||||
| Treiber | Umgebungsvariable | Anwendungsfall | Sicherheitsstufe |
|
||||
| ----------- | -------------------------- | -------------------------------------------------- | ---------------------------------- |
|
||||
| Deaktiviert | `SERVERLESS_TYPE=DISABLED` | Logikfunktionen vollständig deaktivieren | N/A |
|
||||
| Lokal | `SERVERLESS_TYPE=LOCAL` | Entwicklung und vertrauenswürdige Umgebungen | Niedrig (keine Sandbox) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Produktivbetrieb mit nicht vertrauenswürdigem Code | Hoch (Isolation auf Hardwareebene) |
|
||||
| Treiber | Umgebungsvariable | Anwendungsfall | Sicherheitsstufe |
|
||||
| ----------- | ------------------------------ | -------------------------------------------------- | ---------------------------------- |
|
||||
| Deaktiviert | `LOGIC_FUNCTION_TYPE=DISABLED` | Logikfunktionen vollständig deaktivieren | N/A |
|
||||
| Lokal | `LOGIC_FUNCTION_TYPE=LOCAL` | Entwicklung und vertrauenswürdige Umgebungen | Niedrig (keine Sandbox) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Produktivbetrieb mit nicht vertrauenswürdigem Code | Hoch (Isolation auf Hardwareebene) |
|
||||
|
||||
### Empfohlene Konfiguration
|
||||
### Logikfunktionen - Empfohlene Konfiguration
|
||||
|
||||
**Für die Entwicklung:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**Für den Produktivbetrieb (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Zum Deaktivieren von Logikfunktionen:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### Code-Interpreter - Verfügbare Treiber
|
||||
|
||||
| Treiber | Umgebungsvariable | Anwendungsfall | Sicherheitsstufe |
|
||||
| ----------- | -------------------------------- | ------------------------------------------ | ------------------------ |
|
||||
| Deaktiviert | `CODE_INTERPRETER_TYPE=DISABLED` | KI-Codeausführung deaktivieren | N/A |
|
||||
| Lokal | `CODE_INTERPRETER_TYPE=LOCAL` | Nur für die Entwicklung | Niedrig (keine Sandbox) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Produktion mit Ausführung in einer Sandbox | Hoch (isolierte Sandbox) |
|
||||
|
||||
<Note>
|
||||
Bei Verwendung von `SERVERLESS_TYPE=DISABLED` führt jeder Versuch, eine Logikfunktion auszuführen, zu einem Fehler. Dies ist nützlich, wenn Sie Twenty ohne Unterstützung für Logikfunktionen betreiben möchten.
|
||||
Bei Verwendung von `LOGIC_FUNCTION_TYPE=DISABLED` oder `CODE_INTERPRETER_TYPE=DISABLED` führt jeder Ausführungsversuch zu einem Fehler. Dies ist nützlich, wenn Sie Twenty ohne diese Funktionen betreiben möchten.
|
||||
</Note>
|
||||
|
||||
@@ -296,46 +296,60 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Modalità solo ambiente:** Se imposti `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, aggiungi queste variabili al tuo file `.env` invece.
|
||||
</Warning>
|
||||
|
||||
## Funzioni logiche
|
||||
## Funzioni logiche & interprete del codice
|
||||
|
||||
Twenty supporta le funzioni logiche per i workflow e la logica personalizzata. L'ambiente di esecuzione è configurato tramite la variabile di ambiente `SERVERLESS_TYPE`.
|
||||
Twenty supporta le funzioni logiche per i workflow e l'interprete del codice per l'analisi dei dati con IA. Entrambi eseguono codice fornito dall'utente e richiedono una configurazione esplicita per motivi di sicurezza.
|
||||
|
||||
### Impostazioni di sicurezza predefinite
|
||||
|
||||
**In produzione (NODE_ENV=production):** Sia le funzioni logiche sia l'interprete del codice hanno come impostazione predefinita **Disabilitato**. È necessario abilitarli esplicitamente con `LOGIC_FUNCTION_TYPE` e `CODE_INTERPRETER_TYPE` se queste funzionalità sono necessarie.
|
||||
|
||||
**In sviluppo (NODE_ENV=development):** Entrambi sono impostati su **LOCAL** per comodità quando vengono eseguiti in locale.
|
||||
|
||||
<Warning>
|
||||
**Avviso di sicurezza:** Il driver locale (`SERVERLESS_TYPE=LOCAL`) esegue il codice direttamente sull'host in un processo Node.js senza sandboxing. Dovrebbe essere utilizzato solo per codice attendibile in fase di sviluppo. Per le distribuzioni in produzione che gestiscono codice non attendibile, consigliamo vivamente di usare `SERVERLESS_TYPE=LAMBDA` o `SERVERLESS_TYPE=DISABLED`.
|
||||
**Avviso di sicurezza:** Il driver locale (`LOGIC_FUNCTION_TYPE=LOCAL` o `CODE_INTERPRETER_TYPE=LOCAL`) esegue il codice direttamente sull'host in un processo Node.js senza sandboxing. Dovrebbe essere utilizzato solo per codice attendibile in fase di sviluppo. Per le distribuzioni in produzione che gestiscono codice non affidabile, usa `LOGIC_FUNCTION_TYPE=LAMBDA` o `CODE_INTERPRETER_TYPE=E2B` (con sandboxing), oppure lasciali disabilitati.
|
||||
</Warning>
|
||||
|
||||
### Driver disponibili
|
||||
### Funzioni logiche - Driver disponibili
|
||||
|
||||
| Driver | Variabile di ambiente | Caso d'uso | Livello di sicurezza |
|
||||
| ------------ | -------------------------- | -------------------------------------------- | ------------------------------------ |
|
||||
| Disabilitato | `SERVERLESS_TYPE=DISABLED` | Disabilita completamente le funzioni logiche | N/A |
|
||||
| Locale | `SERVERLESS_TYPE=LOCAL` | Ambienti di sviluppo e attendibili | Basso (nessuna sandbox) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Produzione con codice non attendibile | Alto (isolamento a livello hardware) |
|
||||
| Driver | Variabile di ambiente | Caso d'uso | Livello di sicurezza |
|
||||
| ------------ | ------------------------------ | -------------------------------------------- | ------------------------------------ |
|
||||
| Disabilitato | `LOGIC_FUNCTION_TYPE=DISABLED` | Disabilita completamente le funzioni logiche | N/A |
|
||||
| Locale | `LOGIC_FUNCTION_TYPE=LOCAL` | Ambienti di sviluppo e attendibili | Basso (nessuna sandbox) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Produzione con codice non attendibile | Alto (isolamento a livello hardware) |
|
||||
|
||||
### Configurazione consigliata
|
||||
### Funzioni logiche - Configurazione consigliata
|
||||
|
||||
**Per lo sviluppo:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**Per la produzione (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Per disabilitare le funzioni logiche:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### Interprete del codice - Driver disponibili
|
||||
|
||||
| Driver | Variabile di ambiente | Caso d'uso | Livello di sicurezza |
|
||||
| ------------ | -------------------------------- | ------------------------------------- | ----------------------- |
|
||||
| Disabilitato | `CODE_INTERPRETER_TYPE=DISABLED` | Disabilita l'esecuzione del codice IA | N/A |
|
||||
| Locale | `CODE_INTERPRETER_TYPE=LOCAL` | Solo per lo sviluppo | Basso (nessuna sandbox) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Produzione con esecuzione in sandbox | Alta (sandbox isolata) |
|
||||
|
||||
<Note>
|
||||
Quando si utilizza `SERVERLESS_TYPE=DISABLED`, qualsiasi tentativo di eseguire una funzione logica restituirà un errore. Ciò è utile se si desidera eseguire Twenty senza il supporto per le funzioni logiche.
|
||||
Quando si utilizza `LOGIC_FUNCTION_TYPE=DISABLED` o `CODE_INTERPRETER_TYPE=DISABLED`, qualsiasi tentativo di esecuzione restituirà un errore. Ciò è utile se si desidera eseguire Twenty senza queste funzionalità.
|
||||
</Note>
|
||||
|
||||
@@ -297,46 +297,60 @@ 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>
|
||||
|
||||
## Funções lógicas
|
||||
## Funções lógicas e interpretador de código
|
||||
|
||||
O Twenty oferece suporte a funções lógicas para fluxos de trabalho e lógica personalizada. O ambiente de execução é configurado por meio da variável de ambiente `SERVERLESS_TYPE`.
|
||||
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.
|
||||
|
||||
### Padrões de segurança
|
||||
|
||||
**Em produção (NODE_ENV=production):** Tanto as funções lógicas quanto o interpretador de código têm como padrão **Desativado**. Você deve habilitá-los explicitamente com `LOGIC_FUNCTION_TYPE` e `CODE_INTERPRETER_TYPE` se precisar desses recursos.
|
||||
|
||||
**Em desenvolvimento (NODE_ENV=development):** Ambos têm como padrão **LOCAL** por conveniência ao executar localmente.
|
||||
|
||||
<Warning>
|
||||
**Aviso de segurança:** O driver local (`SERVERLESS_TYPE=LOCAL`) executa código diretamente no host em um processo Node.js sem sandbox. Deve ser usado apenas para código confiável em desenvolvimento. Para implantações de produção que lidam com código não confiável, recomendamos fortemente usar `SERVERLESS_TYPE=LAMBDA` ou `SERVERLESS_TYPE=DISABLED`.
|
||||
**Aviso de segurança:** O driver local (`LOGIC_FUNCTION_TYPE=LOCAL` ou `CODE_INTERPRETER_TYPE=LOCAL`) executa código diretamente no host em um processo Node.js sem sandbox. Deve ser usado apenas para código confiável em desenvolvimento. Para implantações de produção que lidam com código não confiável, use `LOGIC_FUNCTION_TYPE=LAMBDA` ou `CODE_INTERPRETER_TYPE=E2B` (com sandbox), ou mantenha-os desativados.
|
||||
</Warning>
|
||||
|
||||
### Drivers disponíveis
|
||||
### Funções lógicas - Drivers disponíveis
|
||||
|
||||
| Driver | Variável de ambiente | Caso de uso | Nível de segurança |
|
||||
| ---------- | -------------------------- | ------------------------------------------ | -------------------------------------- |
|
||||
| Desativado | `SERVERLESS_TYPE=DISABLED` | Desativar completamente as funções lógicas | N/A |
|
||||
| Local | `SERVERLESS_TYPE=LOCAL` | Desenvolvimento e ambientes confiáveis | Baixo (sem sandbox) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Produção com código não confiável | Alto (isolamento em nível de hardware) |
|
||||
| Driver | Variável de ambiente | Caso de uso | Nível de segurança |
|
||||
| ---------- | ------------------------------ | ------------------------------------------ | -------------------------------------- |
|
||||
| Desativado | `LOGIC_FUNCTION_TYPE=DISABLED` | Desativar completamente as funções lógicas | N/A |
|
||||
| Local | `LOGIC_FUNCTION_TYPE=LOCAL` | Desenvolvimento e ambientes confiáveis | Baixo (sem sandbox) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Produção com código não confiável | Alto (isolamento em nível de hardware) |
|
||||
|
||||
### Configuração recomendada
|
||||
### Funções lógicas - Configuração recomendada
|
||||
|
||||
**Para desenvolvimento:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**Para produção (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Para desativar as funções lógicas:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### Interpretador de código - Drivers disponíveis
|
||||
|
||||
| Driver | Variável de ambiente | Caso de uso | Nível de segurança |
|
||||
| ---------- | -------------------------------- | ------------------------------------ | ---------------------- |
|
||||
| Desativado | `CODE_INTERPRETER_TYPE=DISABLED` | Desativar a execução de código de IA | N/A |
|
||||
| Local | `CODE_INTERPRETER_TYPE=LOCAL` | Apenas para desenvolvimento | Baixo (sem sandbox) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Produção com execução em sandbox | Alto (sandbox isolado) |
|
||||
|
||||
<Note>
|
||||
Ao usar `SERVERLESS_TYPE=DISABLED`, qualquer tentativa de executar uma função lógica retornará um erro. Isso é útil se você quiser executar o Twenty sem recursos de funções lógicas.
|
||||
Ao usar `LOGIC_FUNCTION_TYPE=DISABLED` ou `CODE_INTERPRETER_TYPE=DISABLED`, qualquer tentativa de execução retornará um erro. Isso é útil se você quiser executar o Twenty sem esses recursos.
|
||||
</Note>
|
||||
|
||||
@@ -297,46 +297,60 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Mod doar pentru mediu:** Dacă setați `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, adăugați aceste variabile în fișierul dvs. `.env` în schimb.
|
||||
</Warning>
|
||||
|
||||
## Funcții logice
|
||||
## Funcții logice și interpretor de cod
|
||||
|
||||
Twenty acceptă funcții logice pentru fluxuri de lucru și logică personalizată. Mediul de execuție este configurat prin variabila de mediu `SERVERLESS_TYPE`.
|
||||
Twenty acceptă funcții logice pentru fluxuri de lucru și interpretorul de cod pentru analiza datelor cu AI. Ambele rulează cod furnizat de utilizator și necesită o configurare explicită din motive de securitate.
|
||||
|
||||
### Valori implicite de securitate
|
||||
|
||||
**În producție (NODE_ENV=production):** Atât funcțiile logice, cât și interpretorul de cod au implicit valoarea **Dezactivat**. Trebuie să le activezi explicit cu `LOGIC_FUNCTION_TYPE` și `CODE_INTERPRETER_TYPE` dacă ai nevoie de aceste funcționalități.
|
||||
|
||||
**În dezvoltare (NODE_ENV=development):** Ambele au implicit valoarea **LOCAL** pentru comoditate când rulezi local.
|
||||
|
||||
<Warning>
|
||||
**Atenționare de securitate:** Driverul local (`SERVERLESS_TYPE=LOCAL`) rulează codul direct pe gazdă într-un proces Node.js, fără sandboxing. Ar trebui utilizat doar pentru cod de încredere, în dezvoltare. Pentru implementări de producție care gestionează cod neverificat, recomandăm cu tărie utilizarea `SERVERLESS_TYPE=LAMBDA` sau `SERVERLESS_TYPE=DISABLED`.
|
||||
**Atenționare de securitate:** Driverul local (`LOGIC_FUNCTION_TYPE=LOCAL` sau `CODE_INTERPRETER_TYPE=LOCAL`) rulează codul direct pe gazdă într-un proces Node.js, fără sandboxing. Ar trebui utilizat doar pentru cod de încredere, în dezvoltare. Pentru implementări în producție care gestionează cod neverificat, folosiți `LOGIC_FUNCTION_TYPE=LAMBDA` sau `CODE_INTERPRETER_TYPE=E2B` (cu sandboxing) ori păstrați-le dezactivate.
|
||||
</Warning>
|
||||
|
||||
### Drivere disponibile
|
||||
### Funcții logice - Drivere disponibile
|
||||
|
||||
| Driver | Variabilă de mediu | Caz de utilizare | Nivel de securitate |
|
||||
| ---------- | -------------------------- | ------------------------------------- | -------------------------------------- |
|
||||
| Dezactivat | `SERVERLESS_TYPE=DISABLED` | Dezactivează complet funcțiile logice | N/A |
|
||||
| Local | `SERVERLESS_TYPE=LOCAL` | Dezvoltare și medii de încredere | Scăzut (fără sandboxing) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Producție cu cod neverificat | Ridicat (izolare la nivel de hardware) |
|
||||
| Driver | Variabilă de mediu | Caz de utilizare | Nivel de securitate |
|
||||
| ---------- | ------------------------------ | ------------------------------------- | -------------------------------------- |
|
||||
| Dezactivat | `LOGIC_FUNCTION_TYPE=DISABLED` | Dezactivează complet funcțiile logice | N/A |
|
||||
| Local | `LOGIC_FUNCTION_TYPE=LOCAL` | Dezvoltare și medii de încredere | Scăzut (fără sandboxing) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Producție cu cod neverificat | Ridicat (izolare la nivel de hardware) |
|
||||
|
||||
### Configurație recomandată
|
||||
### Funcții logice - Configurare recomandată
|
||||
|
||||
**Pentru dezvoltare:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**Pentru producție (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Pentru a dezactiva funcțiile logice:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### Interpretor de cod - Drivere disponibile
|
||||
|
||||
| Driver | Variabilă de mediu | Caz de utilizare | Nivel de securitate |
|
||||
| ---------- | -------------------------------- | ---------------------------------------- | ------------------------ |
|
||||
| Dezactivat | `CODE_INTERPRETER_TYPE=DISABLED` | Dezactivați execuția codului de către AI | N/A |
|
||||
| Local | `CODE_INTERPRETER_TYPE=LOCAL` | Doar pentru dezvoltare | Scăzut (fără sandboxing) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Producție cu execuție în sandbox | Ridicat (sandbox izolat) |
|
||||
|
||||
<Note>
|
||||
Când se utilizează `SERVERLESS_TYPE=DISABLED`, orice încercare de a executa o funcție logică va returna o eroare. Acest lucru este util dacă doriți să rulați Twenty fără capabilități de funcții logice.
|
||||
Când utilizați `LOGIC_FUNCTION_TYPE=DISABLED` sau `CODE_INTERPRETER_TYPE=DISABLED`, orice încercare de execuție va returna o eroare. Acest lucru este util dacă doriți să rulați Twenty fără aceste capabilități.
|
||||
</Note>
|
||||
|
||||
@@ -296,46 +296,60 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Режим только для среды:** если вы установили `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, добавьте эти переменные в свой `.env` файл вместо этого.
|
||||
</Warning>
|
||||
|
||||
## Логические функции
|
||||
## Логические функции и интерпретатор кода
|
||||
|
||||
Twenty поддерживает логические функции для рабочих процессов и пользовательской логики. Среда выполнения настраивается через переменную окружения `SERVERLESS_TYPE`.
|
||||
Twenty поддерживает логические функции для рабочих процессов и интерпретатор кода для анализа данных ИИ. Оба запускают предоставленный пользователем код и требуют явной настройки в целях безопасности.
|
||||
|
||||
### Настройки безопасности по умолчанию
|
||||
|
||||
**В продакшене (NODE_ENV=production):** логические функции и интерпретатор кода по умолчанию — **Отключено**. Если вам нужны эти функции, вы должны явно включить их с помощью `LOGIC_FUNCTION_TYPE` и `CODE_INTERPRETER_TYPE`.
|
||||
|
||||
**В разработке (NODE_ENV=development):** оба по умолчанию — **LOCAL** для удобства при локальном запуске.
|
||||
|
||||
<Warning>
|
||||
**Уведомление о безопасности:** локальный драйвер (`SERVERLESS_TYPE=LOCAL`) выполняет код напрямую на хосте в процессе Node.js без изоляции. Его следует использовать только для доверенного кода в разработке. Для промышленных развертываний, обрабатывающих недоверенный код, настоятельно рекомендуем использовать `SERVERLESS_TYPE=LAMBDA` или `SERVERLESS_TYPE=DISABLED`.
|
||||
**Уведомление о безопасности:** локальный драйвер (`LOGIC_FUNCTION_TYPE=LOCAL` или `CODE_INTERPRETER_TYPE=LOCAL`) выполняет код напрямую на хосте в процессе Node.js без изоляции. Его следует использовать только для доверенного кода в разработке. Для рабочих развёртываний, обрабатывающих недоверенный код, используйте `LOGIC_FUNCTION_TYPE=LAMBDA` или `CODE_INTERPRETER_TYPE=E2B` (с песочницей) либо оставьте их отключёнными.
|
||||
</Warning>
|
||||
|
||||
### Доступные драйверы
|
||||
### Логические функции — доступные драйверы
|
||||
|
||||
| Драйвер | Переменная окружения | Сценарий использования | Уровень безопасности |
|
||||
| --------- | -------------------------- | -------------------------------------- | ----------------------------------------- |
|
||||
| Отключено | `SERVERLESS_TYPE=DISABLED` | Полностью отключить логические функции | Н/Д |
|
||||
| Локальный | `SERVERLESS_TYPE=LOCAL` | Разработка и доверенные среды | Низкий (без изоляции) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Продакшн с недоверенным кодом | Высокий (изоляция на уровне оборудования) |
|
||||
| Драйвер | Переменная окружения | Сценарий использования | Уровень безопасности |
|
||||
| --------- | ------------------------------ | -------------------------------------- | ----------------------------------------- |
|
||||
| Отключено | `LOGIC_FUNCTION_TYPE=DISABLED` | Полностью отключить логические функции | Н/Д |
|
||||
| Локальный | `LOGIC_FUNCTION_TYPE=LOCAL` | Разработка и доверенные среды | Низкий (без изоляции) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Продакшн с недоверенным кодом | Высокий (изоляция на уровне оборудования) |
|
||||
|
||||
### Рекомендуемая конфигурация
|
||||
### Логические функции — рекомендуемая конфигурация
|
||||
|
||||
**Для разработки:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**Для продакшна (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Чтобы отключить логические функции:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### Интерпретатор кода — доступные драйверы
|
||||
|
||||
| Драйвер | Переменная окружения | Сценарий использования | Уровень безопасности |
|
||||
| --------- | -------------------------------- | ----------------------------------------------------- | --------------------------------- |
|
||||
| Отключено | `CODE_INTERPRETER_TYPE=DISABLED` | Отключить выполнение кода ИИ | Н/Д |
|
||||
| Локальный | `CODE_INTERPRETER_TYPE=LOCAL` | Только для разработки | Низкий (без изоляции) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Рабочая среда с изолированным исполнением в песочнице | Высокий (изолированная песочница) |
|
||||
|
||||
<Note>
|
||||
При использовании `SERVERLESS_TYPE=DISABLED` любая попытка выполнить логическую функцию вернет ошибку. Это полезно, если вы хотите запускать Twenty без возможностей логических функций.
|
||||
При использовании `LOGIC_FUNCTION_TYPE=DISABLED` или `CODE_INTERPRETER_TYPE=DISABLED` любая попытка выполнения вернёт ошибку. Это полезно, если вы хотите запускать Twenty без этих возможностей.
|
||||
</Note>
|
||||
|
||||
@@ -297,46 +297,60 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**Çevre-yalnızca modu:** `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` ayarlarsanız, bu değişkenleri `.env` dosyanıza ekleyin.
|
||||
</Warning>
|
||||
|
||||
## Mantıksal işlevler
|
||||
## Mantıksal İşlevler ve Kod Yorumlayıcısı
|
||||
|
||||
Twenty, iş akışları ve özel mantık için mantıksal işlevleri destekler. Yürütme ortamı, `SERVERLESS_TYPE` ortam değişkeni aracılığıyla yapılandırılır.
|
||||
Twenty, iş akışları için mantıksal işlevleri ve yapay zekâ veri analizi için kod yorumlayıcısını destekler. Her ikisi de kullanıcı tarafından sağlanan kodu çalıştırır ve güvenlik için açık bir yapılandırma gerektirir.
|
||||
|
||||
### Güvenlik Varsayılanları
|
||||
|
||||
**Üretimde (NODE_ENV=production):** Hem mantıksal işlevler hem de kod yorumlayıcı varsayılan olarak **Devre Dışı**dır. Bu özelliklere ihtiyacınız varsa, onları `LOGIC_FUNCTION_TYPE` ve `CODE_INTERPRETER_TYPE` ile açıkça etkinleştirmeniz gerekir.
|
||||
|
||||
**Geliştirmede (NODE_ENV=development):** Yerel olarak çalıştırırken kolaylık olması için her ikisinin varsayılanı **LOCAL**dır.
|
||||
|
||||
<Warning>
|
||||
**Güvenlik Notu:** Yerel sürücü (`SERVERLESS_TYPE=LOCAL`), herhangi bir sandbox olmadan kodu ana makinede doğrudan bir Node.js sürecinde çalıştırır. Yalnızca geliştirirken güvenilen kod için kullanılmalıdır. Güvenilmeyen kodu işleyen üretim dağıtımları için `SERVERLESS_TYPE=LAMBDA` veya `SERVERLESS_TYPE=DISABLED` kullanmanızı şiddetle öneririz.
|
||||
**Güvenlik Notu:** Yerel sürücü (`LOGIC_FUNCTION_TYPE=LOCAL` veya `CODE_INTERPRETER_TYPE=LOCAL`), herhangi bir sandbox olmadan kodu ana makinede doğrudan bir Node.js sürecinde çalıştırır. Yalnızca geliştirirken güvenilen kod için kullanılmalıdır. Üretim dağıtımlarında güvenilmeyen kodu işlerken, `LOGIC_FUNCTION_TYPE=LAMBDA` veya `CODE_INTERPRETER_TYPE=E2B` (korumalı alanla) kullanın ya da bunları devre dışı bırakın.
|
||||
</Warning>
|
||||
|
||||
### Kullanılabilir Sürücüler
|
||||
### Mantıksal İşlevler - Kullanılabilir Sürücüler
|
||||
|
||||
| Sürücü | Ortam Değişkeni | Kullanım alanı | Güvenlik Düzeyi |
|
||||
| ---------- | -------------------------- | ---------------------------------------------- | ------------------------------------ |
|
||||
| Devre dışı | `SERVERLESS_TYPE=DISABLED` | Mantıksal işlevleri tamamen devre dışı bırakın | Uygulanamaz |
|
||||
| Yerel | `SERVERLESS_TYPE=LOCAL` | Geliştirme ve güvenilir ortamlar | Düşük (sandbox yok) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Güvenilmeyen kodla üretim | Yüksek (donanım düzeyinde izolasyon) |
|
||||
| Sürücü | Ortam Değişkeni | Kullanım alanı | Güvenlik Düzeyi |
|
||||
| ---------- | ------------------------------ | ---------------------------------------------- | ------------------------------------ |
|
||||
| Devre dışı | `LOGIC_FUNCTION_TYPE=DISABLED` | Mantıksal işlevleri tamamen devre dışı bırakın | Uygulanamaz |
|
||||
| Yerel | `LOGIC_FUNCTION_TYPE=LOCAL` | Geliştirme ve güvenilir ortamlar | Düşük (sandbox yok) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Güvenilmeyen kodla üretim | Yüksek (donanım düzeyinde izolasyon) |
|
||||
|
||||
### Önerilen Yapılandırma
|
||||
### Mantıksal İşlevler - Önerilen Yapılandırma
|
||||
|
||||
**Geliştirme için:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**Üretim için (AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**Mantıksal işlevleri devre dışı bırakmak için:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### Kod Yorumlayıcısı - Kullanılabilir Sürücüler
|
||||
|
||||
| Sürücü | Ortam Değişkeni | Kullanım Senaryosu | Güvenlik Düzeyi |
|
||||
| ---------- | -------------------------------- | --------------------------------------------- | --------------------------------- |
|
||||
| Devre dışı | `CODE_INTERPRETER_TYPE=DISABLED` | Yapay zekâ kod yürütmesini devre dışı bırakın | Uygulanamaz |
|
||||
| Yerel | `CODE_INTERPRETER_TYPE=LOCAL` | Yalnızca geliştirme için | Düşük (sandbox yok) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Korumalı alanlı yürütmeyle üretim | Yüksek (yalıtılmış korumalı alan) |
|
||||
|
||||
<Note>
|
||||
`SERVERLESS_TYPE=DISABLED` kullanıldığında, bir mantıksal işlevi yürütmeye yönelik herhangi bir girişim bir hata döndürür. Bu, Twenty'yi mantıksal işlev yetenekleri olmadan çalıştırmak istiyorsanız kullanışlıdır.
|
||||
`LOGIC_FUNCTION_TYPE=DISABLED` veya `CODE_INTERPRETER_TYPE=DISABLED` kullanıldığında, herhangi bir yürütme girişimi bir hata döndürür. Bu, Twenty'yi bu yetenekler olmadan çalıştırmak istiyorsanız kullanışlıdır.
|
||||
</Note>
|
||||
|
||||
@@ -297,46 +297,60 @@ yarn command:prod cron:workflow:automated-cron-trigger
|
||||
**仅限环境模式:** 如果你设置 `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`,请将这些变量添加到 `.env` 文件中。
|
||||
</Warning>
|
||||
|
||||
## 逻辑函数
|
||||
## 逻辑函数与代码解释器
|
||||
|
||||
Twenty 支持用于工作流和自定义逻辑的逻辑函数。 执行环境通过 `SERVERLESS_TYPE` 环境变量进行配置。
|
||||
Twenty 支持用于工作流的逻辑函数,以及用于 AI 数据分析的代码解释器。 二者都会运行用户提供的代码,并要求进行显式配置以确保安全。
|
||||
|
||||
### 安全默认设置
|
||||
|
||||
**在生产环境(NODE_ENV=production):** 逻辑函数和代码解释器的默认设置为**禁用**。 如需这些功能,必须通过 `LOGIC_FUNCTION_TYPE` 和 `CODE_INTERPRETER_TYPE` 显式启用它们。
|
||||
|
||||
**在开发环境(NODE_ENV=development):** 为方便在本地运行,二者默认均为**LOCAL**。
|
||||
|
||||
<Warning>
|
||||
\*\*安全提示:\*\*本地驱动(`SERVERLESS_TYPE=LOCAL`)在没有沙箱的情况下,在主机上的 Node.js 进程中直接运行代码。 它应仅在开发环境中用于可信代码。 对于在生产环境中处理不受信任代码的部署,我们强烈建议使用 `SERVERLESS_TYPE=LAMBDA` 或 `SERVERLESS_TYPE=DISABLED`。
|
||||
**安全提示:** 本地驱动(`LOGIC_FUNCTION_TYPE=LOCAL` 或 `CODE_INTERPRETER_TYPE=LOCAL`)会在没有沙箱的情况下,在主机上的 Node.js 进程中直接运行代码。 它应仅在开发环境中用于可信代码。 对于在生产环境中处理不受信任代码的部署,请使用 `LOGIC_FUNCTION_TYPE=LAMBDA` 或 `CODE_INTERPRETER_TYPE=E2B`(使用沙盒),或将它们保持禁用。
|
||||
</Warning>
|
||||
|
||||
### 可用驱动
|
||||
### 逻辑函数 - 可用驱动程序
|
||||
|
||||
| 驱动 | 环境变量 | 用例 | 安全级别 |
|
||||
| ------ | -------------------------- | -------------- | -------- |
|
||||
| 禁用 | `SERVERLESS_TYPE=DISABLED` | 完全禁用逻辑函数 | 不适用 |
|
||||
| 本地 | `SERVERLESS_TYPE=LOCAL` | 开发和可信环境 | 低(无沙箱) |
|
||||
| Lambda | `SERVERLESS_TYPE=LAMBDA` | 生产环境(处理不受信任代码) | 高(硬件级隔离) |
|
||||
| 驱动 | 环境变量 | 用例 | 安全级别 |
|
||||
| ------ | ------------------------------ | -------------- | -------- |
|
||||
| 禁用 | `LOGIC_FUNCTION_TYPE=DISABLED` | 完全禁用逻辑函数 | 不适用 |
|
||||
| 本地 | `LOGIC_FUNCTION_TYPE=LOCAL` | 开发和可信环境 | 低(无沙箱) |
|
||||
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | 生产环境(处理不受信任代码) | 高(硬件级隔离) |
|
||||
|
||||
### 推荐配置
|
||||
### 逻辑函数 - 推荐配置
|
||||
|
||||
**用于开发:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LOCAL # default
|
||||
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
|
||||
```
|
||||
|
||||
**用于生产(AWS):**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=LAMBDA
|
||||
SERVERLESS_LAMBDA_REGION=us-east-1
|
||||
SERVERLESS_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
LOGIC_FUNCTION_TYPE=LAMBDA
|
||||
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
|
||||
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
|
||||
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
|
||||
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
|
||||
```
|
||||
|
||||
**要禁用逻辑函数:**
|
||||
|
||||
```bash
|
||||
SERVERLESS_TYPE=DISABLED
|
||||
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
|
||||
```
|
||||
|
||||
### 代码解释器 - 可用驱动程序
|
||||
|
||||
| 驱动 | 环境变量 | 用例 | 安全级别 |
|
||||
| --- | -------------------------------- | ----------- | ------- |
|
||||
| 禁用 | `CODE_INTERPRETER_TYPE=DISABLED` | 禁用 AI 代码执行 | 不适用 |
|
||||
| 本地 | `CODE_INTERPRETER_TYPE=LOCAL` | 仅限开发环境 | 低(无沙箱) |
|
||||
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | 生产环境(沙盒化执行) | 高(隔离沙盒) |
|
||||
|
||||
<Note>
|
||||
使用 `SERVERLESS_TYPE=DISABLED` 时,任何尝试执行逻辑函数的操作都会返回错误。 如果你想在不启用逻辑函数能力的情况下运行 Twenty,这将很有用。
|
||||
当使用 `LOGIC_FUNCTION_TYPE=DISABLED` 或 `CODE_INTERPRETER_TYPE=DISABLED` 时,任何执行尝试都会返回错误。 如果你想在不启用这些功能的情况下运行 Twenty,这将很有用。
|
||||
</Note>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledHeaderSkeleton = styled.div`
|
||||
align-items: center;
|
||||
background: ${themeCssVariables.background.noisy};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
justify-content: space-between;
|
||||
min-height: ${PAGE_BAR_MIN_HEIGHT}px;
|
||||
padding: ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledHeaderLeft = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const PageContentSkeletonLoader = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledHeaderSkeleton>
|
||||
<StyledHeaderLeft>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
width={104}
|
||||
/>
|
||||
</SkeletonTheme>
|
||||
</StyledHeaderLeft>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton
|
||||
width={132}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
</SkeletonTheme>
|
||||
</StyledHeaderSkeleton>
|
||||
<PageBody>{null}</PageBody>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,100 +1,14 @@
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import {
|
||||
MOBILE_VIEWPORT,
|
||||
ThemeContext,
|
||||
themeCssVariables,
|
||||
} from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledMainContainer = styled.div`
|
||||
background: ${themeCssVariables.background.noisy};
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
padding-left: 12px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPanel = styled.div`
|
||||
background: ${themeCssVariables.background.primary};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledHeaderContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
const StyledRightPanelContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledRightPanelFlexContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 32px;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
const StyledSkeletonHeaderLoader = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledHeaderContainer>
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
width={104}
|
||||
/>
|
||||
</SkeletonTheme>
|
||||
</StyledHeaderContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledSkeletonAddLoader = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Skeleton width={132} height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} />
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
|
||||
const RightPanelSkeleton = () => (
|
||||
<StyledMainContainer>
|
||||
<StyledPanel></StyledPanel>
|
||||
</StyledMainContainer>
|
||||
);
|
||||
|
||||
export const RightPanelSkeletonLoader = () => (
|
||||
<StyledRightPanelContainer>
|
||||
<StyledRightPanelFlexContainer>
|
||||
<StyledSkeletonHeaderLoader />
|
||||
<StyledSkeletonAddLoader />
|
||||
</StyledRightPanelFlexContainer>
|
||||
<RightPanelSkeleton />
|
||||
<PageContentSkeletonLoader />
|
||||
</StyledRightPanelContainer>
|
||||
);
|
||||
|
||||
@@ -13,11 +13,9 @@ const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
height: 100dvh;
|
||||
min-width: ${NAVIGATION_DRAWER_CONSTRAINTS.default}px;
|
||||
overflow: hidden;
|
||||
padding: 12px 8px 12px 8px;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
@@ -25,6 +23,11 @@ const StyledContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLeftPanelWrapper = styled.div`
|
||||
flex-shrink: 0;
|
||||
padding: 12px 0 12px 8px;
|
||||
`;
|
||||
|
||||
export const UserOrMetadataLoader = () => {
|
||||
const showAuthModal = useShowAuthModal();
|
||||
|
||||
@@ -36,7 +39,9 @@ export const UserOrMetadataLoader = () => {
|
||||
backdropZIndex={RootStackingContextZIndices.RootModalBackDrop}
|
||||
/>
|
||||
)}
|
||||
<LeftPanelSkeletonLoader />
|
||||
<StyledLeftPanelWrapper>
|
||||
<LeftPanelSkeletonLoader />
|
||||
</StyledLeftPanelWrapper>
|
||||
<RightPanelSkeletonLoader />
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2
@@ -157,10 +157,12 @@ export const CalendarEventDetails = ({
|
||||
const renderField = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
const isReadOnly = isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
isSystemObject: objectMetadataItem.isSystem,
|
||||
objectPermissions,
|
||||
fieldMetadataItem: {
|
||||
id: fieldMetadataItem.id,
|
||||
isUIReadOnly: fieldMetadataItem.isUIReadOnly ?? false,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+28
-24
@@ -1,35 +1,39 @@
|
||||
import { downloadFile } from '@/activities/files/utils/downloadFile';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
jest.mock('file-saver', () => ({
|
||||
saveAs: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockBlob = new Blob(['test content'], { type: 'application/pdf' });
|
||||
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
status: 200,
|
||||
blob: jest.fn(),
|
||||
blob: () => Promise.resolve(mockBlob),
|
||||
} as unknown as Response),
|
||||
);
|
||||
|
||||
window.URL.createObjectURL = jest.fn(() => 'mock-url');
|
||||
window.URL.revokeObjectURL = jest.fn();
|
||||
|
||||
// FIXME: jest is behaving weirdly here, it's not finding the element
|
||||
// Also the document's innerHTML is empty
|
||||
// `global.fetch` and `window.fetch` are also undefined
|
||||
describe.skip('downloadFile', () => {
|
||||
it('should download a file', () => {
|
||||
downloadFile('url/to/file.pdf', 'file.pdf');
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith('url/to/file.pdf');
|
||||
|
||||
const link = document.querySelector(
|
||||
'a[href="mock-url"][download="file.pdf"]',
|
||||
);
|
||||
|
||||
expect(link).not.toBeNull();
|
||||
// oxlint-disable-next-line @typescripttypescript/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expect(link?.style?.display).toBe('none');
|
||||
|
||||
expect(link).toHaveBeenCalledTimes(1);
|
||||
|
||||
describe('downloadFile', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should download a file', async () => {
|
||||
await downloadFile('url/to/file.pdf', 'file.pdf');
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith('url/to/file.pdf');
|
||||
expect(saveAs).toHaveBeenCalledWith(mockBlob, 'file.pdf');
|
||||
});
|
||||
|
||||
it('should reject when fetch fails', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValueOnce({
|
||||
status: 404,
|
||||
blob: () => Promise.resolve(mockBlob),
|
||||
});
|
||||
|
||||
await expect(downloadFile('url/to/file.pdf', 'file.pdf')).rejects.toBe(
|
||||
'Failed downloading file',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export const downloadFile = (fullPath: string, fileName: string) => {
|
||||
fetch(fullPath)
|
||||
return fetch(fullPath)
|
||||
.then((resp) =>
|
||||
resp.status === 200
|
||||
? resp.blob()
|
||||
|
||||
@@ -24,6 +24,7 @@ import { mapDBMessagesToUIMessages } from '@/ai/utils/mapDBMessagesToUIMessages'
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
type GetChatThreadsQuery,
|
||||
GetChatThreadsDocument,
|
||||
@@ -181,7 +182,11 @@ export const useAgentChatData = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const isNewThread = currentAIChatThread === AGENT_CHAT_NEW_THREAD_DRAFT_KEY;
|
||||
const isNewThread = useMemo(
|
||||
() => currentAIChatThread === AGENT_CHAT_NEW_THREAD_DRAFT_KEY,
|
||||
[currentAIChatThread],
|
||||
);
|
||||
|
||||
const { loading: messagesLoading, data } = useGetChatMessagesQuery({
|
||||
variables: { threadId: currentAIChatThread! },
|
||||
skip: !isDefined(currentAIChatThread) || isNewThread,
|
||||
@@ -191,7 +196,7 @@ export const useAgentChatData = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const ensureThreadForDraft = () => {
|
||||
const ensureThreadForDraft = useCallback(() => {
|
||||
const current = store.get(currentAIChatThreadState.atom);
|
||||
if (current !== AGENT_CHAT_NEW_THREAD_DRAFT_KEY) {
|
||||
return;
|
||||
@@ -218,9 +223,16 @@ export const useAgentChatData = () => {
|
||||
threadIdPromise.finally(() => {
|
||||
setPendingCreateFromDraftPromise(null);
|
||||
});
|
||||
};
|
||||
}, [
|
||||
createChatThread,
|
||||
setPendingCreateFromDraftPromise,
|
||||
store,
|
||||
setIsCreatingChatThread,
|
||||
]);
|
||||
|
||||
const ensureThreadIdForSend = async (): Promise<string | null> => {
|
||||
const ensureThreadIdForSend = useCallback(async (): Promise<
|
||||
string | null
|
||||
> => {
|
||||
const current = store.get(currentAIChatThreadState.atom);
|
||||
if (current !== AGENT_CHAT_NEW_THREAD_DRAFT_KEY) {
|
||||
return current;
|
||||
@@ -247,16 +259,33 @@ export const useAgentChatData = () => {
|
||||
} finally {
|
||||
setIsCreatingChatThread(false);
|
||||
}
|
||||
};
|
||||
}, [createChatThread, store, setIsCreatingChatThread]);
|
||||
|
||||
const uiMessages = mapDBMessagesToUIMessages(data?.chatMessages || []);
|
||||
const isLoading = messagesLoading || threadsLoading;
|
||||
const threadsLoadingMemoized = useMemo(
|
||||
() => threadsLoading,
|
||||
[threadsLoading],
|
||||
);
|
||||
|
||||
const messagesLoadingMemoized = useMemo(
|
||||
() => messagesLoading,
|
||||
[messagesLoading],
|
||||
);
|
||||
|
||||
const uiMessages = useMemo(
|
||||
() => mapDBMessagesToUIMessages(data?.chatMessages || []),
|
||||
[data?.chatMessages],
|
||||
);
|
||||
|
||||
const isLoading = useMemo(
|
||||
() => messagesLoadingMemoized || threadsLoadingMemoized,
|
||||
[messagesLoadingMemoized, threadsLoadingMemoized],
|
||||
);
|
||||
|
||||
return {
|
||||
uiMessages,
|
||||
isLoading,
|
||||
threadsLoading,
|
||||
messagesLoading,
|
||||
threadsLoading: threadsLoadingMemoized,
|
||||
messagesLoading: messagesLoadingMemoized,
|
||||
ensureThreadForDraft,
|
||||
ensureThreadIdForSend,
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AI_CHAT_SCROLL_WRAPPER_ID } from '@/ai/constants/AiChatScrollWrapperId'
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollBottomComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const SCROLL_BOTTOM_THRESHOLD_PX = 10;
|
||||
@@ -16,9 +17,12 @@ export const useAgentChatScrollToBottom = () => {
|
||||
AI_CHAT_SCROLL_WRAPPER_ID,
|
||||
);
|
||||
|
||||
const isNearBottom = scrollWrapperScrollBottom <= SCROLL_BOTTOM_THRESHOLD_PX;
|
||||
const isNearBottom = useMemo(
|
||||
() => scrollWrapperScrollBottom <= SCROLL_BOTTOM_THRESHOLD_PX,
|
||||
[scrollWrapperScrollBottom],
|
||||
);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
const scrollToBottom = useCallback(() => {
|
||||
const { scrollWrapperElement } = getScrollWrapperElement();
|
||||
if (!isDefined(scrollWrapperElement)) {
|
||||
return;
|
||||
@@ -27,7 +31,7 @@ export const useAgentChatScrollToBottom = () => {
|
||||
scrollWrapperElement.scrollTo({
|
||||
top: scrollWrapperElement.scrollHeight,
|
||||
});
|
||||
};
|
||||
}, [getScrollWrapperElement]);
|
||||
|
||||
return { scrollToBottom, isNearBottom };
|
||||
};
|
||||
|
||||
@@ -80,9 +80,26 @@ export const useProcessUIToolCallMessage = () => {
|
||||
|
||||
break;
|
||||
}
|
||||
case 'navigateToView':
|
||||
// TODO: implement
|
||||
case 'navigateToView': {
|
||||
const viewObjectNamePlural = objectMetadataItems.find(
|
||||
(item) =>
|
||||
item.nameSingular === navigateAppOutput.objectNameSingular,
|
||||
)?.namePlural;
|
||||
|
||||
if (!isDefined(viewObjectNamePlural)) {
|
||||
throw new Error(
|
||||
`Object with singular name ${navigateAppOutput.objectNameSingular} not found, cannot navigate to view from chat.`,
|
||||
);
|
||||
}
|
||||
|
||||
navigateApp(
|
||||
AppPath.RecordIndexPage,
|
||||
{ objectNamePlural: viewObjectNamePlural },
|
||||
{ viewId: navigateAppOutput.viewId },
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'wait': {
|
||||
await sleep(navigateAppOutput.durationMs);
|
||||
break;
|
||||
|
||||
@@ -58,6 +58,9 @@ const mockWorkspace = {
|
||||
isPasswordAuthBypassEnabled: false,
|
||||
isMicrosoftAuthBypassEnabled: false,
|
||||
hasValidEnterpriseKey: false,
|
||||
hasActivatedAndValidEnterpriseKey: false,
|
||||
hasValidSignedEnterpriseKey: false,
|
||||
hasValidEnterpriseValidityToken: false,
|
||||
subdomain: 'test',
|
||||
customDomain: 'test.com',
|
||||
workspaceUrls: {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { type ReactNode, Suspense } from 'react';
|
||||
import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader';
|
||||
|
||||
type LazyRouteProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const LazyRouteFallback = () => (
|
||||
<PageContainer>
|
||||
<PageContentSkeletonLoader />
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
export const LazyRoute = ({ children }: LazyRouteProps) => (
|
||||
<Suspense fallback={<></>}>{children}</Suspense>
|
||||
<Suspense fallback={<LazyRouteFallback />}>{children}</Suspense>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsProtectedRouteWrapper';
|
||||
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
|
||||
import { SettingPublicDomain } from '@/settings/domains/components/SettingPublicDomain';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
PermissionFlagType,
|
||||
@@ -662,6 +663,15 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
|
||||
{isAdminPageEnabled && (
|
||||
<>
|
||||
<Route path={SettingsPath.AdminPanel} element={<SettingsAdmin />} />
|
||||
<Route
|
||||
path={SettingsPath.Enterprise}
|
||||
element={
|
||||
<Navigate
|
||||
to={getSettingsPath(SettingsPath.AdminPanelEnterprise)}
|
||||
replace
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.AdminPanelIndicatorHealthStatus}
|
||||
element={<SettingsAdminIndicatorHealthStatus />}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user