Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aac099f2e8 | ||
|
|
f262437da6 | ||
|
|
15d0970f72 | ||
|
|
1adc325887 | ||
|
|
5726bd6e17 | ||
|
|
102b49f919 | ||
|
|
2af3121c51 | ||
|
|
a024a04e01 | ||
|
|
f0c83434a7 | ||
|
|
b699619756 | ||
|
|
b2f053490d | ||
|
|
21de221420 | ||
|
|
a3c0c62506 | ||
|
|
d9b3507866 | ||
|
|
b346f4fb59 | ||
|
|
390ae34ed8 | ||
|
|
2c5af2654d | ||
|
|
6cbc7725b7 | ||
|
|
744ef3aa9d | ||
|
|
fe59c333ac | ||
|
|
6c0da189cb | ||
|
|
ef92d2d321 | ||
|
|
00c3cd1051 | ||
|
|
99f885306e | ||
|
|
413d1124bb | ||
|
|
5aea16f070 | ||
|
|
ab5fb1f658 | ||
|
|
2f542d6763 | ||
|
|
4ac38449cb | ||
|
|
dab4cbcb7d | ||
|
|
a73f586619 | ||
|
|
d1126f507d | ||
|
|
e4e7137660 | ||
|
|
7fb8cc1c39 | ||
|
|
1d95670252 | ||
|
|
f855dacec9 | ||
|
|
3a1f10e572 | ||
|
|
0fe7e9d5fe | ||
|
|
d16299a624 | ||
|
|
61d058bb1f | ||
|
|
2de022afcf | ||
|
|
25d9f2fcce | ||
|
|
6e628bb751 | ||
|
|
e7fe435f60 | ||
|
|
40a8d18d38 | ||
|
|
621962e049 | ||
|
|
7b9939b43e | ||
|
|
ec1f08bc3b | ||
|
|
dd58eb6814 | ||
|
|
926dd545f4 | ||
|
|
8e003aa6cf | ||
|
|
982f0c4a4d | ||
|
|
d1c95e380e | ||
|
|
6399e11220 | ||
|
|
033d297695 | ||
|
|
dee55b635f | ||
|
|
045faf018a | ||
|
|
4370788023 | ||
|
|
1656bb5568 | ||
|
|
59e9563fc7 | ||
|
|
c73a660e46 | ||
|
|
05c2da2d0f | ||
|
|
6995420b71 | ||
|
|
f9f7e2f929 | ||
|
|
8bbda86eb6 | ||
|
|
6f6a9a55fb | ||
|
|
882e9fd231 | ||
|
|
ae122f4bb1 | ||
|
|
c433b2b73f | ||
|
|
309fd7a526 | ||
|
|
5ab3eeb830 | ||
|
|
75bb3a904d | ||
|
|
82a1179e23 | ||
|
|
662de17644 | ||
|
|
1993614637 | ||
|
|
a3aae5c857 | ||
|
|
22a203680e | ||
|
|
36bcc71f3d | ||
|
|
06bdb5ad6a | ||
|
|
a9696705c1 | ||
|
|
0acc08c333 | ||
|
|
c2a79fc0c2 | ||
|
|
5b28e59ca7 |
@@ -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
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
|
||||
front-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
needs: front-sb-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -11,7 +11,7 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
jobs:
|
||||
e2e-test:
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
tasks: ${{ matrix.task }}
|
||||
sdk-e2e-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, sdk-test]
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
services:
|
||||
|
||||
@@ -25,6 +25,7 @@ jobs:
|
||||
packages/twenty-server/**
|
||||
packages/twenty-front/src/generated/**
|
||||
packages/twenty-front/src/generated-metadata/**
|
||||
packages/twenty-sdk/src/clients/generated/metadata/**
|
||||
packages/twenty-emails/**
|
||||
packages/twenty-shared/**
|
||||
|
||||
@@ -32,7 +33,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
@@ -60,7 +61,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
@@ -79,7 +80,7 @@ jobs:
|
||||
server-validation:
|
||||
needs: server-build
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
@@ -158,8 +159,10 @@ jobs:
|
||||
|
||||
exit 1
|
||||
fi
|
||||
- name: GraphQL / Check for Pending Generation
|
||||
- name: Check for Pending Code Generation
|
||||
run: |
|
||||
HAS_ERRORS=false
|
||||
|
||||
npx nx run twenty-front:graphql:generate
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
|
||||
@@ -171,15 +174,29 @@ jobs:
|
||||
git diff -- packages/twenty-front/src/generated packages/twenty-front/src/generated-metadata
|
||||
echo "==================================================="
|
||||
echo ""
|
||||
echo "Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes."
|
||||
echo ""
|
||||
exit 1
|
||||
HAS_ERRORS=true
|
||||
fi
|
||||
|
||||
npx nx run twenty-sdk:generate-metadata-client
|
||||
|
||||
if ! git diff --quiet -- packages/twenty-sdk/src/clients/generated/metadata; then
|
||||
echo "::error::SDK metadata client changes detected. Please run 'npx nx run twenty-sdk:generate-metadata-client' and commit the changes."
|
||||
echo ""
|
||||
echo "The following SDK metadata client changes were detected:"
|
||||
echo "==================================================="
|
||||
git diff -- packages/twenty-sdk/src/clients/generated/metadata
|
||||
echo "==================================================="
|
||||
echo ""
|
||||
HAS_ERRORS=true
|
||||
fi
|
||||
|
||||
if [ "$HAS_ERRORS" = true ]; then
|
||||
exit 1
|
||||
fi
|
||||
server-test:
|
||||
needs: server-build
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
@@ -201,7 +218,7 @@ jobs:
|
||||
|
||||
server-integration-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
needs: server-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
name: CI Twenty Standard Front Component
|
||||
|
||||
on:
|
||||
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: |
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-standard-application/**
|
||||
packages/twenty-sdk/**
|
||||
packages/twenty-shared/**
|
||||
|
||||
standard-front-component-build-check:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build twenty-shared
|
||||
run: npx nx build twenty-shared
|
||||
- name: Build twenty-sdk
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Build twenty-standard-application
|
||||
run: npx nx build twenty-standard-application
|
||||
- name: Check for pending standard front component build
|
||||
run: |
|
||||
if ! git diff --quiet -- packages/twenty-standard-application/src/build packages/twenty-standard-application/src/standard-front-component-build-manifest.ts; then
|
||||
echo "::error::Standard front component build output is out of date. Please run 'npx nx build twenty-standard-application' and commit the changes."
|
||||
echo ""
|
||||
echo "The following changes were detected:"
|
||||
echo "==================================================="
|
||||
git diff -- packages/twenty-standard-application/src/build packages/twenty-standard-application/src/standard-front-component-build-manifest.ts
|
||||
echo "==================================================="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ci-twenty-standard-front-component-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
standard-front-component-build-check,
|
||||
]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,6 +207,7 @@
|
||||
"packages/twenty-e2e-testing",
|
||||
"packages/twenty-shared",
|
||||
"packages/twenty-sdk",
|
||||
"packages/twenty-standard-application",
|
||||
"packages/twenty-apps",
|
||||
"packages/twenty-cli",
|
||||
"packages/create-twenty-app",
|
||||
|
||||
@@ -43,7 +43,7 @@ yarn twenty auth:login
|
||||
yarn twenty entity:add
|
||||
|
||||
# Start dev mode: watches, builds, and syncs local changes to your workspace
|
||||
# (also auto-generates typed API clients — CoreApiClient and MetadataApiClient — in node_modules/twenty-sdk/generated)
|
||||
# (also auto-generates typed CoreApiClient — MetadataApiClient ships pre-built with the SDK — both available via `twenty-sdk/clients`)
|
||||
yarn twenty app:dev
|
||||
|
||||
# Watch your application's function logs
|
||||
@@ -107,7 +107,7 @@ npx create-twenty-app@latest my-app -m
|
||||
- Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
|
||||
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
|
||||
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
|
||||
- Two typed API clients are auto‑generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
- `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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.6.4",
|
||||
"version": "0.7.0-canary.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,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
|
||||
## UUID requirement
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -113,6 +135,7 @@ export class CreateAppCommand {
|
||||
includeExampleView: false,
|
||||
includeExampleNavigationMenuItem: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleAgent: false,
|
||||
includeExampleIntegrationTest: false,
|
||||
};
|
||||
}
|
||||
@@ -126,6 +149,7 @@ export class CreateAppCommand {
|
||||
includeExampleNavigationMenuItem: true,
|
||||
includeExampleSkill: true,
|
||||
includeExampleIntegrationTest: true,
|
||||
includeExampleAgent: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@ export type ExampleOptions = {
|
||||
includeExampleView: boolean;
|
||||
includeExampleNavigationMenuItem: boolean;
|
||||
includeExampleSkill: boolean;
|
||||
includeExampleAgent: boolean;
|
||||
includeExampleIntegrationTest: boolean;
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ const ALL_EXAMPLES: ExampleOptions = {
|
||||
includeExampleView: true,
|
||||
includeExampleNavigationMenuItem: true,
|
||||
includeExampleSkill: true,
|
||||
includeExampleAgent: true,
|
||||
includeExampleIntegrationTest: true,
|
||||
};
|
||||
|
||||
@@ -41,6 +42,7 @@ const NO_EXAMPLES: ExampleOptions = {
|
||||
includeExampleObject: false,
|
||||
includeExampleField: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleAgent: false,
|
||||
includeExampleLogicFunction: false,
|
||||
includeExampleFrontComponent: false,
|
||||
includeExampleView: false,
|
||||
@@ -384,6 +386,12 @@ describe('copyBaseApplicationProject', () => {
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(testAppDirectory, '.github', 'workflows', 'ci.yml'),
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
// Install functions should always exist
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
@@ -462,6 +470,12 @@ describe('copyBaseApplicationProject', () => {
|
||||
join(srcPath, '__tests__', 'app-install.integration-test.ts'),
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(testAppDirectory, '.github', 'workflows', 'ci.yml'),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -476,6 +490,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
includeExampleObject: false,
|
||||
includeExampleField: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleAgent: false,
|
||||
includeExampleLogicFunction: false,
|
||||
includeExampleFrontComponent: true,
|
||||
includeExampleView: false,
|
||||
@@ -513,6 +528,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
exampleOptions: {
|
||||
includeExampleObject: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleAgent: false,
|
||||
includeExampleField: false,
|
||||
includeExampleLogicFunction: true,
|
||||
includeExampleFrontComponent: false,
|
||||
|
||||
@@ -47,18 +47,17 @@ describe('scaffoldIntegrationTest', () => {
|
||||
const content = await fs.readFile(testPath, 'utf8');
|
||||
|
||||
expect(content).toContain(
|
||||
"import { appGenerateClient, appUninstall } from 'twenty-sdk/cli'",
|
||||
"import { appBuild, appUninstall } from 'twenty-sdk/cli'",
|
||||
);
|
||||
expect(content).toContain(
|
||||
"import { MetadataApiClient } from 'twenty-sdk/generated'",
|
||||
"import { MetadataApiClient } from 'twenty-sdk/clients'",
|
||||
);
|
||||
expect(content).toContain(
|
||||
"import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'",
|
||||
);
|
||||
expect(content).toContain('TWENTY_TEST_API_KEY');
|
||||
expect(content).toContain('assertServerIsReachable');
|
||||
expect(content).toContain('appGenerateClient');
|
||||
expect(content).toContain('appBuild');
|
||||
expect(content).toContain('appUninstall');
|
||||
expect(content).toContain('new MetadataApiClient()');
|
||||
expect(content).toContain('findManyApplications');
|
||||
expect(content).toContain('APPLICATION_UNIVERSAL_IDENTIFIER');
|
||||
});
|
||||
@@ -84,7 +83,8 @@ describe('scaffoldIntegrationTest', () => {
|
||||
expect(content).toContain('.twenty-sdk-test');
|
||||
expect(content).toContain('config.json');
|
||||
expect(content).toContain('process.env.TWENTY_API_URL');
|
||||
expect(content).toContain('process.env.TWENTY_TEST_API_KEY');
|
||||
expect(content).toContain('process.env.TWENTY_API_KEY');
|
||||
expect(content).toContain('assertServerIsReachable');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,7 +101,8 @@ describe('scaffoldIntegrationTest', () => {
|
||||
|
||||
const content = await fs.readFile(vitestConfigPath, 'utf8');
|
||||
|
||||
expect(content).toContain('TWENTY_TEST_API_KEY');
|
||||
expect(content).toContain('TWENTY_API_KEY');
|
||||
expect(content).not.toContain('TWENTY_TEST_API_KEY');
|
||||
expect(content).toContain('TWENTY_API_URL');
|
||||
expect(content).toContain('setup-test.ts');
|
||||
expect(content).toContain('tsconfig.spec.json');
|
||||
@@ -109,6 +110,37 @@ describe('scaffoldIntegrationTest', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('github workflow', () => {
|
||||
it('should create .github/workflows/ci.yml with correct structure', async () => {
|
||||
await scaffoldIntegrationTest({
|
||||
appDirectory: testAppDirectory,
|
||||
sourceFolderPath,
|
||||
});
|
||||
|
||||
const workflowPath = join(
|
||||
testAppDirectory,
|
||||
'.github',
|
||||
'workflows',
|
||||
'ci.yml',
|
||||
);
|
||||
|
||||
expect(await fs.pathExists(workflowPath)).toBe(true);
|
||||
|
||||
const content = await fs.readFile(workflowPath, 'utf8');
|
||||
|
||||
expect(content).toContain('name: CI');
|
||||
expect(content).toContain('TWENTY_VERSION: latest');
|
||||
expect(content).toContain('twenty-version: ${{ env.TWENTY_VERSION }}');
|
||||
expect(content).toContain('actions/checkout@v4');
|
||||
expect(content).toContain('spawn-twenty-docker-image@main');
|
||||
expect(content).toContain('actions/setup-node@v4');
|
||||
expect(content).toContain('yarn install --immutable');
|
||||
expect(content).toContain('yarn test');
|
||||
expect(content).toContain('TWENTY_API_URL');
|
||||
expect(content).toContain('TWENTY_TEST_API_KEY');
|
||||
});
|
||||
});
|
||||
|
||||
describe('tsconfig.spec.json', () => {
|
||||
it('should create tsconfig.spec.json extending the base tsconfig', async () => {
|
||||
await scaffoldIntegrationTest({
|
||||
|
||||
@@ -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);
|
||||
@@ -103,6 +103,14 @@ export const copyBaseApplicationProject = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (exampleOptions.includeExampleAgent) {
|
||||
await createExampleAgent({
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'agents',
|
||||
fileName: 'example-agent.ts',
|
||||
});
|
||||
}
|
||||
|
||||
if (exampleOptions.includeExampleIntegrationTest) {
|
||||
await scaffoldIntegrationTest({
|
||||
appDirectory,
|
||||
@@ -134,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.
|
||||
|
||||
@@ -519,6 +520,36 @@ export default defineSkill({
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createExampleAgent = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
|
||||
const content = `import { defineAgent } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_AGENT_UNIVERSAL_IDENTIFIER =
|
||||
'${universalIdentifier}';
|
||||
|
||||
export default defineAgent({
|
||||
universalIdentifier: EXAMPLE_AGENT_UNIVERSAL_IDENTIFIER,
|
||||
name: 'example-agent',
|
||||
label: 'Example Agent',
|
||||
description: 'A sample AI agent for your application',
|
||||
icon: 'IconRobot',
|
||||
prompt: 'You are a helpful assistant. Help users with their questions and tasks.',
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createApplicationConfig = async ({
|
||||
displayName,
|
||||
description,
|
||||
@@ -552,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,
|
||||
|
||||
@@ -25,6 +25,7 @@ export const scaffoldIntegrationTest = async ({
|
||||
|
||||
await createVitestConfig(appDirectory);
|
||||
await createTsconfigSpec(appDirectory);
|
||||
await createGithubWorkflow(appDirectory);
|
||||
};
|
||||
|
||||
const createVitestConfig = async (appDirectory: string) => {
|
||||
@@ -45,7 +46,7 @@ export default defineConfig({
|
||||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||||
env: {
|
||||
TWENTY_API_URL: 'http://localhost:3000',
|
||||
TWENTY_TEST_API_KEY:
|
||||
TWENTY_API_KEY:
|
||||
'${SEED_API_KEY}',
|
||||
},
|
||||
},
|
||||
@@ -93,16 +94,36 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
|
||||
beforeAll(() => {
|
||||
const assertServerIsReachable = async () => {
|
||||
let response: Response;
|
||||
|
||||
try {
|
||||
response = await fetch(\`\${TWENTY_API_URL}/healthz\`);
|
||||
} catch {
|
||||
throw new Error(
|
||||
\`Twenty server is not reachable at \${TWENTY_API_URL}. \` +
|
||||
'Make sure the server is running before executing integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(\`Server at \${TWENTY_API_URL} returned \${response.status}\`);
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await assertServerIsReachable();
|
||||
|
||||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||||
|
||||
const configFile = {
|
||||
profiles: {
|
||||
default: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_TEST_API_KEY,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -128,44 +149,24 @@ const createIntegrationTest = async ({
|
||||
fileName: string;
|
||||
}) => {
|
||||
const content = `import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
|
||||
import { appGenerateClient, appUninstall } from 'twenty-sdk/cli';
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
import { appBuild, appUninstall } from 'twenty-sdk/cli';
|
||||
import { MetadataApiClient } from 'twenty-sdk/clients';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
const APP_PATH = process.cwd();
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
const assertServerIsReachable = async () => {
|
||||
let response: Response;
|
||||
|
||||
try {
|
||||
response = await fetch(\`\${TWENTY_API_URL}/healthz\`);
|
||||
} catch {
|
||||
throw new Error(
|
||||
\`Twenty server is not reachable at \${TWENTY_API_URL}. \` +
|
||||
'Make sure the server is running before executing integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(\`Server at \${TWENTY_API_URL} returned \${response.status}\`);
|
||||
}
|
||||
};
|
||||
|
||||
describe('App installation', () => {
|
||||
let appInstalled = false;
|
||||
|
||||
beforeAll(async () => {
|
||||
await assertServerIsReachable();
|
||||
|
||||
const generateResult = await appGenerateClient({
|
||||
const buildResult = await appBuild({
|
||||
appPath: APP_PATH,
|
||||
onProgress: (message: string) => console.log(\`[generate-client] \${message}\`),
|
||||
onProgress: (message: string) => console.log(\`[build] \${message}\`),
|
||||
});
|
||||
|
||||
if (!generateResult.success) {
|
||||
if (!buildResult.success) {
|
||||
throw new Error(
|
||||
\`Client generation failed: \${generateResult.error?.message ?? 'Unknown error'}\`,
|
||||
\`Build failed: \${buildResult.error?.message ?? 'Unknown error'}\`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,20 +188,7 @@ describe('App installation', () => {
|
||||
});
|
||||
|
||||
it('should find the installed app in the applications list', async () => {
|
||||
const apiKey = process.env.TWENTY_TEST_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error(
|
||||
'No API key found. Set TWENTY_TEST_API_KEY in your vitest config env.',
|
||||
);
|
||||
}
|
||||
|
||||
const metadataClient = new MetadataApiClient({
|
||||
url: \`\${TWENTY_API_URL}/metadata\`,
|
||||
headers: {
|
||||
Authorization: \`Bearer \${apiKey}\`,
|
||||
},
|
||||
});
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
const result = await metadataClient.query({
|
||||
findManyApplications: {
|
||||
@@ -224,3 +212,56 @@ describe('App installation', () => {
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const DEFAULT_TWENTY_VERSION = 'latest';
|
||||
|
||||
const createGithubWorkflow = async (appDirectory: string) => {
|
||||
const content = `name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: ${DEFAULT_TWENTY_VERSION}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
with:
|
||||
twenty-version: \${{ env.TWENTY_VERSION }}
|
||||
github-token: \${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run integration tests
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: \${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_TEST_API_KEY: \${{ steps.twenty.outputs.access-token }}
|
||||
`;
|
||||
|
||||
const workflowDir = join(appDirectory, '.github', 'workflows');
|
||||
|
||||
await fs.ensureDir(workflowDir);
|
||||
await fs.writeFile(join(workflowDir, 'ci.yml'), content);
|
||||
};
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
generated
|
||||
|
||||
# Ignore .twenty build output for all apps
|
||||
.twenty
|
||||
|
||||
# Allow committed fixture metadata (used by dev seeder)
|
||||
!fixtures/**/.twenty
|
||||
fixtures/**/.twenty/*
|
||||
!fixtures/**/.twenty/output
|
||||
fixtures/**/.twenty/output/**
|
||||
!fixtures/**/.twenty/output/manifest.json
|
||||
!fixtures/**/.twenty/output/package.json
|
||||
!fixtures/**/.twenty/output/yarn.lock
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
|
||||
## UUID requirement
|
||||
- All generated UUIDs must be valid UUID v4.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { defineLogicFunction, RoutePayload } from "twenty-sdk";
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
import { MetadataApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
export const OAUTH_TOKEN_PAIRS_PATH = '/oauth/token-pairs';
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import {
|
||||
type DatabaseEventPayload,
|
||||
type ObjectRecordUpdateEvent,
|
||||
} from 'twenty-sdk';
|
||||
import { CoreApiClient } from 'twenty-sdk/generated';
|
||||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
type CompanyRecord = {
|
||||
id: string;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import path from 'path';
|
||||
|
||||
import { buildAndValidateManifest } from '@/cli/utilities/build/manifest/build-and-validate-manifest';
|
||||
import { buildApplication } from '@/cli/utilities/build/common/build-application';
|
||||
import { manifestUpdateChecksums } from '@/cli/utilities/build/manifest/manifest-update-checksums';
|
||||
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
|
||||
|
||||
const FIXTURE_APPS = ['hello-world-app', 'postcard-app', 'minimal-app'];
|
||||
|
||||
const buildFixture = async (fixtureName: string): Promise<void> => {
|
||||
const appPath = path.resolve(__dirname, fixtureName);
|
||||
|
||||
console.log(`\n--- Building fixture: ${fixtureName} ---`);
|
||||
console.log(` App path: ${appPath}`);
|
||||
|
||||
const manifestResult = await buildAndValidateManifest(appPath);
|
||||
|
||||
if (!manifestResult.success) {
|
||||
console.error(` FAILED to build manifest: ${manifestResult.errors.join(', ')}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { manifest, filePaths } = manifestResult;
|
||||
|
||||
for (const warning of manifestResult.warnings) {
|
||||
console.warn(` Warning: ${warning}`);
|
||||
}
|
||||
|
||||
const buildResult = await buildApplication({ appPath, manifest, filePaths });
|
||||
|
||||
const updatedManifest = manifestUpdateChecksums({
|
||||
manifest,
|
||||
builtFileInfos: buildResult.builtFileInfos,
|
||||
});
|
||||
|
||||
await writeManifestToOutput(appPath, updatedManifest);
|
||||
|
||||
console.log(` Built ${buildResult.builtFileInfos.size} files`);
|
||||
console.log(` Output: ${path.join(appPath, '.twenty', 'output')}`);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
for (const fixture of FIXTURE_APPS) {
|
||||
await buildFixture(fixture);
|
||||
}
|
||||
|
||||
console.log('\nDone.');
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineApplication } from '@/sdk';
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './my.role';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineRole } from '@/sdk';
|
||||
import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'9f992b6c-17e9-4381-bab5-b6150b574304';
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
export const ADD_NUMBERS_UNIVERSAL_IDENTIFIER =
|
||||
'f9e5589c-e951-4d99-85db-0a305ab53502';
|
||||
+2
-2
@@ -3,13 +3,13 @@
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["../../../../../src/*"]
|
||||
"src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*"],
|
||||
@@ -0,0 +1,2 @@
|
||||
generated
|
||||
node_modules
|
||||
@@ -0,0 +1,404 @@
|
||||
{
|
||||
"application": {
|
||||
"universalIdentifier": "6563e091-9f5b-4026-a3ea-7e3b3d09e218",
|
||||
"displayName": "Hello world",
|
||||
"description": "",
|
||||
"defaultRoleUniversalIdentifier": "9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f",
|
||||
"yarnLockChecksum": null,
|
||||
"packageJsonChecksum": null,
|
||||
"apiClientChecksum": "6db455b2541abbd2d4e9f85469bdc532",
|
||||
"preInstallLogicFunctionUniversalIdentifier": "1272ffdb-8e2f-492c-ab37-66c2b97e9c23",
|
||||
"postInstallLogicFunctionUniversalIdentifier": "7a3f4684-51db-494d-833b-a747a3b90507"
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"universalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"nameSingular": "exampleItem",
|
||||
"namePlural": "exampleItems",
|
||||
"labelSingular": "Example item",
|
||||
"labelPlural": "Example items",
|
||||
"description": "A sample custom object",
|
||||
"icon": "IconBox",
|
||||
"labelIdentifierFieldMetadataUniversalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
|
||||
"fields": [
|
||||
{
|
||||
"universalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
|
||||
"type": "TEXT",
|
||||
"name": "name",
|
||||
"label": "Name",
|
||||
"description": "Name of the example item",
|
||||
"icon": "IconAbc"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"label": "Id",
|
||||
"description": "Id",
|
||||
"icon": "Icon123",
|
||||
"isNullable": false,
|
||||
"defaultValue": "uuid",
|
||||
"type": "UUID",
|
||||
"universalIdentifier": "5ba62991-6034-555c-b984-5e84897eaa9f"
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"label": "Creation date",
|
||||
"description": "Creation date",
|
||||
"icon": "IconCalendar",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "8b616045-237c-574d-8689-73f06acff8d8"
|
||||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"label": "Last update",
|
||||
"description": "Last time the record was changed",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "ced5587d-d9de-51bf-9ad0-74af6198fca4"
|
||||
},
|
||||
{
|
||||
"name": "deletedAt",
|
||||
"label": "Deleted at",
|
||||
"description": "Deletion date",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "674ee694-0b0f-557f-993c-2b1404db0811"
|
||||
},
|
||||
{
|
||||
"name": "createdBy",
|
||||
"label": "Created by",
|
||||
"description": "The creator of the record",
|
||||
"icon": "IconCreativeCommonsSa",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "5ed1503f-2803-59d1-9b27-e4f1701b8c2a"
|
||||
},
|
||||
{
|
||||
"name": "updatedBy",
|
||||
"label": "Updated by",
|
||||
"description": "The workspace member who last updated the record",
|
||||
"icon": "IconUserCircle",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "3d3a80b4-14a0-55a0-b534-1b23f043c1d5"
|
||||
},
|
||||
{
|
||||
"name": "position",
|
||||
"label": "Position",
|
||||
"description": "Position",
|
||||
"icon": "IconHierarchy2",
|
||||
"isNullable": false,
|
||||
"defaultValue": 0,
|
||||
"type": "POSITION",
|
||||
"universalIdentifier": "a714b336-d765-5dff-b34b-fcc6ab037e3b"
|
||||
},
|
||||
{
|
||||
"name": "searchVector",
|
||||
"label": "Search vector",
|
||||
"icon": "IconSearch",
|
||||
"description": "Search vector",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "TS_VECTOR",
|
||||
"universalIdentifier": "068064e1-c9da-5772-9293-9e57183cee67"
|
||||
},
|
||||
{
|
||||
"name": "timelineActivities",
|
||||
"label": "Timeline Activities",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "e5feca94-536c-5779-8c33-05e39705bcd7",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3d140ea1-fc57-5c8c-96fd-7d1525ed6a69",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5"
|
||||
},
|
||||
{
|
||||
"name": "favorites",
|
||||
"label": "Favorites",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "3781cc69-9d3f-58d0-927c-c1c552f37a1d",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3043fec0-091c-59ff-95ef-b9d95fbfa3e6",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860"
|
||||
},
|
||||
{
|
||||
"name": "attachments",
|
||||
"label": "Attachments",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "3ad98da8-42e1-50e3-b73b-8c06fa720e2a",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "e1232464-8f83-5459-aa94-a40cda8d949a",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a"
|
||||
},
|
||||
{
|
||||
"name": "noteTargets",
|
||||
"label": "Note Targets",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "67cae4ab-7d3d-5a66-8d06-ca24568ebe76",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400"
|
||||
},
|
||||
{
|
||||
"name": "taskTargets",
|
||||
"label": "Task Targets",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "a3b9f10c-f780-58cc-bbc9-402ed5f136f6",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "5d46d303-452d-5c9c-ab64-0a9768b87956",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"objectUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"universalIdentifier": "770d32c2-cf12-4ab2-b66d-73f92dc239b5",
|
||||
"type": "NUMBER",
|
||||
"name": "priority",
|
||||
"label": "Priority",
|
||||
"description": "Priority level for the example item (1-10)"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconTimelineEvent",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "3d140ea1-fc57-5c8c-96fd-7d1525ed6a69",
|
||||
"objectUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "e5feca94-536c-5779-8c33-05e39705bcd7",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconHeart",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "3043fec0-091c-59ff-95ef-b9d95fbfa3e6",
|
||||
"objectUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3781cc69-9d3f-58d0-927c-c1c552f37a1d",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "RELATION"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconFileImport",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "e1232464-8f83-5459-aa94-a40cda8d949a",
|
||||
"objectUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3ad98da8-42e1-50e3-b73b-8c06fa720e2a",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f634-435d-ab8d-e1168b375c69"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318",
|
||||
"objectUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "67cae4ab-7d3d-5a66-8d06-ca24568ebe76",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f635-435d-ab8d-e1168b375c70"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "5d46d303-452d-5c9c-ab64-0a9768b87956",
|
||||
"objectUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "a3b9f10c-f780-58cc-bbc9-402ed5f136f6",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f636-435d-ab8d-e1168b375c71"
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"universalIdentifier": "9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f",
|
||||
"label": "Hello world default function role",
|
||||
"description": "Hello world default function role",
|
||||
"canReadAllObjectRecords": true,
|
||||
"canUpdateAllObjectRecords": true,
|
||||
"canSoftDeleteAllObjectRecords": true,
|
||||
"canDestroyAllObjectRecords": false
|
||||
}
|
||||
],
|
||||
"skills": [
|
||||
{
|
||||
"universalIdentifier": "d0940029-9d3c-40be-903a-52d65393028f",
|
||||
"name": "example-skill",
|
||||
"label": "Example Skill",
|
||||
"description": "A sample skill for your application",
|
||||
"icon": "IconBrain",
|
||||
"content": "Add your skill instructions here. Skills provide context and capabilities to AI agents."
|
||||
}
|
||||
],
|
||||
"agents": [],
|
||||
"logicFunctions": [
|
||||
{
|
||||
"universalIdentifier": "2baa26eb-9aaf-4856-a4f4-30d6fd6480ee",
|
||||
"name": "hello-world-logic-function",
|
||||
"description": "A simple logic function",
|
||||
"timeoutSeconds": 5,
|
||||
"httpRouteTriggerSettings": {
|
||||
"path": "/hello-world-logic-function",
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false
|
||||
},
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "src/logic-functions/hello-world.ts",
|
||||
"builtHandlerPath": "src/logic-functions/hello-world.mjs",
|
||||
"builtHandlerChecksum": "f1a0565e18366e6c531438631137d23b"
|
||||
},
|
||||
{
|
||||
"universalIdentifier": "7a3f4684-51db-494d-833b-a747a3b90507",
|
||||
"name": "post-install",
|
||||
"description": "Runs after installation to set up the application.",
|
||||
"timeoutSeconds": 300,
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "src/logic-functions/post-install.ts",
|
||||
"builtHandlerPath": "src/logic-functions/post-install.mjs",
|
||||
"builtHandlerChecksum": "7b016a1819c4e2355a80a895464032a5"
|
||||
},
|
||||
{
|
||||
"universalIdentifier": "1272ffdb-8e2f-492c-ab37-66c2b97e9c23",
|
||||
"name": "pre-install",
|
||||
"description": "Runs before installation to prepare the application.",
|
||||
"timeoutSeconds": 300,
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "src/logic-functions/pre-install.ts",
|
||||
"builtHandlerPath": "src/logic-functions/pre-install.mjs",
|
||||
"builtHandlerChecksum": "e2151e31924929390a5933ebf7419465"
|
||||
}
|
||||
],
|
||||
"frontComponents": [
|
||||
{
|
||||
"universalIdentifier": "d371f098-5b2c-42f0-898d-94459f1ee337",
|
||||
"name": "hello-world-front-component",
|
||||
"description": "A sample front component",
|
||||
"componentName": "HelloWorld",
|
||||
"sourceComponentPath": "src/front-components/hello-world.tsx",
|
||||
"builtComponentPath": "src/front-components/hello-world.mjs",
|
||||
"builtComponentChecksum": "826d5d3f633496d7c2a4f902589b83ed",
|
||||
"isHeadless": false
|
||||
}
|
||||
],
|
||||
"publicAssets": [],
|
||||
"views": [
|
||||
{
|
||||
"universalIdentifier": "e004df40-29f3-47ba-b39d-d3a5c444367a",
|
||||
"name": "All example items",
|
||||
"objectUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"icon": "IconList",
|
||||
"key": "INDEX",
|
||||
"position": 0,
|
||||
"fields": [
|
||||
{
|
||||
"universalIdentifier": "496c40c2-5766-419c-93bf-20fdad3f34bb",
|
||||
"fieldMetadataUniversalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
|
||||
"position": 0,
|
||||
"isVisible": true,
|
||||
"size": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"navigationMenuItems": [
|
||||
{
|
||||
"universalIdentifier": "10f90627-e9c2-44b7-9742-bed77e3d1b17",
|
||||
"name": "example-navigation-menu-item",
|
||||
"icon": "IconList",
|
||||
"color": "blue",
|
||||
"position": 0,
|
||||
"viewUniversalIdentifier": "e004df40-29f3-47ba-b39d-d3a5c444367a"
|
||||
}
|
||||
],
|
||||
"pageLayouts": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "hello-world-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"scripts": {
|
||||
"app:dev": "twenty app:dev",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './src/roles/default-role';
|
||||
|
||||
export const APPLICATION_UNIVERSAL_IDENTIFIER =
|
||||
'6563e091-9f5b-4026-a3ea-7e3b3d09e218';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
displayName: 'Hello world',
|
||||
description: '',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "hello-world-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"scripts": {
|
||||
"app:dev": "twenty app:dev",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineField, FieldType } from 'twenty-sdk';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
|
||||
export default defineField({
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: '770d32c2-cf12-4ab2-b66d-73f92dc239b5',
|
||||
type: FieldType.NUMBER,
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
description: 'Priority level for the example item (1-10)',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const HelloWorld = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>Hello, World!</h1>
|
||||
<p>This is your first front component.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'd371f098-5b2c-42f0-898d-94459f1ee337',
|
||||
name: 'hello-world-front-component',
|
||||
description: 'A sample front component',
|
||||
component: HelloWorld,
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (): Promise<{ message: string }> => {
|
||||
return { message: 'Hello, World!' };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '2baa26eb-9aaf-4856-a4f4-30d6fd6480ee',
|
||||
name: 'hello-world-logic-function',
|
||||
description: 'A simple logic function',
|
||||
timeoutSeconds: 5,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/hello-world-logic-function',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '7a3f4684-51db-494d-833b-a747a3b90507',
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '1272ffdb-8e2f-492c-ab37-66c2b97e9c23',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '10f90627-e9c2-44b7-9742-bed77e3d1b17',
|
||||
name: 'example-navigation-menu-item',
|
||||
icon: 'IconList',
|
||||
color: 'blue',
|
||||
position: 0,
|
||||
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER =
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928';
|
||||
|
||||
export const NAME_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'd2d7f6cd-33f6-456f-bf00-17adeca926ba';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
nameSingular: 'exampleItem',
|
||||
namePlural: 'exampleItems',
|
||||
labelSingular: 'Example item',
|
||||
labelPlural: 'Example items',
|
||||
description: 'A sample custom object',
|
||||
icon: 'IconBox',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.TEXT,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
description: 'Name of the example item',
|
||||
icon: 'IconAbc',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Hello world default function role',
|
||||
description: 'Hello world default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: false,
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineSkill } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER =
|
||||
'd0940029-9d3c-40be-903a-52d65393028f';
|
||||
|
||||
export default defineSkill({
|
||||
universalIdentifier: EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'example-skill',
|
||||
label: 'Example Skill',
|
||||
description: 'A sample skill for your application',
|
||||
icon: 'IconBrain',
|
||||
content: 'Add your skill instructions here. Skills provide context and capabilities to AI agents.',
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
|
||||
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = 'e004df40-29f3-47ba-b39d-d3a5c444367a';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
name: 'All example items',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
icon: 'IconList',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: '496c40c2-5766-419c-93bf-20fdad3f34bb',
|
||||
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["../../../../../src/*"]
|
||||
"src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*"],
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineApplication } from '@/sdk';
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: 'invalid-app-0000-0000-0000-000000000001',
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineObject, FieldType } from '@/sdk';
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
const DUPLICATE_ID = 'duplicate-id-0000-0000-000000000001';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineObject, FieldType } from '@/sdk';
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
const DUPLICATE_ID = 'duplicate-id-0000-0000-000000000001';
|
||||
|
||||
+3
-2
@@ -9,8 +9,9 @@
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["../../../../../src/*"]
|
||||
"src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*"]
|
||||
"include": ["**/*"],
|
||||
"exclude": ["node_modules", "dist", ".twenty"]
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
{
|
||||
"application": {
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000001",
|
||||
"displayName": "Root App",
|
||||
"description": "An app with all entities at root level",
|
||||
"icon": "IconFolder",
|
||||
"defaultRoleUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000002",
|
||||
"yarnLockChecksum": "d41d8cd98f00b204e9800998ecf8427e",
|
||||
"packageJsonChecksum": "ac87abb5113d9d09be71481efc123800",
|
||||
"apiClientChecksum": null
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"nameSingular": "myNote",
|
||||
"namePlural": "myNotes",
|
||||
"labelSingular": "My note",
|
||||
"labelPlural": "My notes",
|
||||
"description": "A simple root-level object",
|
||||
"icon": "IconNote",
|
||||
"labelIdentifierFieldMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000031",
|
||||
"fields": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000031",
|
||||
"type": "TEXT",
|
||||
"label": "Title",
|
||||
"name": "title"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"label": "Id",
|
||||
"description": "Id",
|
||||
"icon": "Icon123",
|
||||
"isNullable": false,
|
||||
"defaultValue": "uuid",
|
||||
"type": "UUID",
|
||||
"universalIdentifier": "5bdb4f57-a349-56d0-b421-845fd55b3026"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Name",
|
||||
"description": "Name",
|
||||
"icon": "IconAbc",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "TEXT",
|
||||
"universalIdentifier": "86cc7b06-26de-5b7b-bf4a-31fa246dfd90"
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"label": "Creation date",
|
||||
"description": "Creation date",
|
||||
"icon": "IconCalendar",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "abe3bc69-1a51-5814-a722-74f5a025e92a"
|
||||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"label": "Last update",
|
||||
"description": "Last time the record was changed",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "c296d408-9720-5a86-8586-3490fbf04b86"
|
||||
},
|
||||
{
|
||||
"name": "deletedAt",
|
||||
"label": "Deleted at",
|
||||
"description": "Deletion date",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "488333a2-f4ff-5790-88ef-21f07f8f1985"
|
||||
},
|
||||
{
|
||||
"name": "createdBy",
|
||||
"label": "Created by",
|
||||
"description": "The creator of the record",
|
||||
"icon": "IconCreativeCommonsSa",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "fba6e01b-6579-554e-ad64-a676003c8e68"
|
||||
},
|
||||
{
|
||||
"name": "updatedBy",
|
||||
"label": "Updated by",
|
||||
"description": "The workspace member who last updated the record",
|
||||
"icon": "IconUserCircle",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "fa60f87d-15c8-5ff7-9da0-191fa14a922f"
|
||||
},
|
||||
{
|
||||
"name": "position",
|
||||
"label": "Position",
|
||||
"description": "Position",
|
||||
"icon": "IconHierarchy2",
|
||||
"isNullable": false,
|
||||
"defaultValue": 0,
|
||||
"type": "POSITION",
|
||||
"universalIdentifier": "4a5d7791-ddc9-53ee-bc46-699da7373290"
|
||||
},
|
||||
{
|
||||
"name": "searchVector",
|
||||
"label": "Search vector",
|
||||
"icon": "IconSearch",
|
||||
"description": "Search vector",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "TS_VECTOR",
|
||||
"universalIdentifier": "0f00bc3b-f313-5206-8496-842af20a538a"
|
||||
},
|
||||
{
|
||||
"name": "timelineActivities",
|
||||
"label": "Timeline Activities",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "7e28b1ea-9a94-5d36-bc03-3f12f476867a",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5"
|
||||
},
|
||||
{
|
||||
"name": "favorites",
|
||||
"label": "Favorites",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "ed80c73b-25ff-5a3c-99c7-e27fa2f2611e",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "698f12fe-3feb-55e3-b6cc-73375f1a8ae6",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860"
|
||||
},
|
||||
{
|
||||
"name": "attachments",
|
||||
"label": "Attachments",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "56a872c8-7a1e-563d-8772-3978c2c5c220",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a"
|
||||
},
|
||||
{
|
||||
"name": "noteTargets",
|
||||
"label": "Note Targets",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "011595e8-7524-5ec7-9fbc-4bd3c42d7d24",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "94b6f269-fc9a-5bcd-af5b-2bdac69f276b",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400"
|
||||
},
|
||||
{
|
||||
"name": "taskTargets",
|
||||
"label": "Task Targets",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "dd734fa3-26c3-53dd-91aa-577c313e1fa4",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconTimelineEvent",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "7e28b1ea-9a94-5d36-bc03-3f12f476867a",
|
||||
"objectUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconHeart",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "698f12fe-3feb-55e3-b6cc-73375f1a8ae6",
|
||||
"objectUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "ed80c73b-25ff-5a3c-99c7-e27fa2f2611e",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "RELATION"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconFileImport",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5",
|
||||
"objectUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "56a872c8-7a1e-563d-8772-3978c2c5c220",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f634-435d-ab8d-e1168b375c69"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "94b6f269-fc9a-5bcd-af5b-2bdac69f276b",
|
||||
"objectUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "011595e8-7524-5ec7-9fbc-4bd3c42d7d24",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f635-435d-ab8d-e1168b375c70"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3",
|
||||
"objectUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "dd734fa3-26c3-53dd-91aa-577c313e1fa4",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f636-435d-ab8d-e1168b375c71"
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000040",
|
||||
"label": "My role",
|
||||
"description": "A simple root-level role",
|
||||
"canReadAllObjectRecords": true,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"canBeAssignedToAgents": false,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canBeAssignedToApiKeys": false
|
||||
}
|
||||
],
|
||||
"skills": [],
|
||||
"agents": [],
|
||||
"logicFunctions": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000010",
|
||||
"name": "my-function",
|
||||
"timeoutSeconds": 5,
|
||||
"httpRouteTriggerSettings": {
|
||||
"path": "/my-function",
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false
|
||||
},
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "my.function.ts",
|
||||
"builtHandlerPath": "my.function.mjs",
|
||||
"builtHandlerChecksum": "71f0af6b9dfeef884cc81ae75ca6ecb0"
|
||||
}
|
||||
],
|
||||
"frontComponents": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000020",
|
||||
"name": "my-component",
|
||||
"description": "A root-level front component",
|
||||
"componentName": "MyComponent",
|
||||
"sourceComponentPath": "my.front-component.tsx",
|
||||
"builtComponentPath": "my.front-component.mjs",
|
||||
"builtComponentChecksum": "37b905c5467584f03fa5ab2c8a05bc88",
|
||||
"isHeadless": false
|
||||
}
|
||||
],
|
||||
"publicAssets": [],
|
||||
"views": [],
|
||||
"navigationMenuItems": [],
|
||||
"pageLayouts": []
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "root-app",
|
||||
"name": "minimal-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineApplication } from '@/sdk';
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000001',
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const myHandler = () => {
|
||||
return 'my-function-result';
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { FieldType, defineObject } from '@/sdk';
|
||||
import { FieldType, defineObject } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineRole } from '@/sdk';
|
||||
import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000040',
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "rich-app",
|
||||
"name": "minimal-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
+3
-2
@@ -9,8 +9,9 @@
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["../../../../../src/*"]
|
||||
"src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*"]
|
||||
"include": ["**/*"],
|
||||
"exclude": ["node_modules", "dist", ".twenty"]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "postcard-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"scripts": {
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineApplication } from '@/sdk';
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './src/roles/default-function.role';
|
||||
|
||||
export default defineApplication({
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "postcard-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"scripts": {
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 570 B |
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { CardDisplay } from '../utils/card-display.component';
|
||||
|
||||
export default defineFrontComponent({
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
import { DEFAULT_NAME, formatGreeting } from '../utils/greeting.util';
|
||||
|
||||
const GreetingComponent = () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const TestComponent = () => {
|
||||
return (
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
POST_CARD_ON_POST_CARD_RECIPIENT_ID,
|
||||
POST_CARD_RECIPIENTS_ON_POST_CARD_ID,
|
||||
} from '@/cli/__tests__/apps/rich-app/src/fields/post-card-recipients-on-post-card.field';
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
|
||||
import { defineField, FieldType, OnDeleteAction, RelationType } from '@/sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
|
||||
} from './post-card-recipients-on-post-card.field';
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
|
||||
import { defineField, FieldType, OnDeleteAction, RelationType } from 'twenty-sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier: POST_CARD_ON_POST_CARD_RECIPIENT_ID,
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
|
||||
import { defineField, FieldType, RelationType } from '@/sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
|
||||
import { defineField, FieldType, RelationType } from 'twenty-sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
export const POST_CARD_RECIPIENTS_ON_POST_CARD_ID =
|
||||
'a1a2b3c4-0001-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { defineField, FieldType, RelationType } from '@/sdk';
|
||||
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/recipient.object';
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
|
||||
import { defineField, FieldType, RelationType } from 'twenty-sdk';
|
||||
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/recipient.object';
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
|
||||
|
||||
export const POST_CARD_RECIPIENTS_ON_RECIPIENT_ID =
|
||||
'a1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { defineField, FieldType } from '@/sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
|
||||
import { defineField, FieldType } from 'twenty-sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
export default defineField({
|
||||
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { defineField, FieldType } from '@/sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
|
||||
import { defineField, FieldType } from 'twenty-sdk';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
export default defineField({
|
||||
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
import { defineField, FieldType, RelationType, OnDeleteAction } from '@/sdk';
|
||||
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/recipient.object';
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '@/cli/__tests__/apps/rich-app/src/objects/post-card-recipient.object';
|
||||
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk';
|
||||
import { RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/recipient.object';
|
||||
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
|
||||
import {
|
||||
POST_CARD_RECIPIENTS_ON_RECIPIENT_ID,
|
||||
RECIPIENT_ON_POST_CARD_RECIPIENT_ID,
|
||||
} from '@/cli/__tests__/apps/rich-app/src/fields/post-card-recipients-on-recipient.field';
|
||||
} from './post-card-recipients-on-recipient.field';
|
||||
|
||||
export default defineField({
|
||||
universalIdentifier: RECIPIENT_ON_POST_CARD_RECIPIENT_ID,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
import { DEFAULT_NAME, formatGreeting } from '../utils/greeting.util';
|
||||
|
||||
const greetingHandler = () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (params: { recipientName: string }) => {
|
||||
return { found: true, name: params.recipientName };
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async () => {
|
||||
return { processed: true };
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
import { testFunction2 } from '../utils/test-function-2.util';
|
||||
|
||||
export default defineLogicFunction({
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
import { formatFarewell } from '../utils/greeting.util';
|
||||
|
||||
const handler = () => {
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { defineNavigationMenuItem } from '@/sdk';
|
||||
import { ALL_POST_CARD_RECIPIENTS_VIEW_ID } from '@/cli/__tests__/apps/rich-app/src/views/all-post-card-recipients.view';
|
||||
import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
import { ALL_POST_CARD_RECIPIENTS_VIEW_ID } from '../views/all-post-card-recipients.view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: 'c1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { defineNavigationMenuItem } from '@/sdk';
|
||||
import { ALL_POST_CARDS_VIEW_ID } from '@/cli/__tests__/apps/rich-app/src/views/all-post-cards.view';
|
||||
import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
import { ALL_POST_CARDS_VIEW_ID } from '../views/all-post-cards.view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: 'c1a2b3c4-0001-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { defineNavigationMenuItem } from '@/sdk';
|
||||
import { ALL_RECIPIENTS_VIEW_ID } from '@/cli/__tests__/apps/rich-app/src/views/all-recipients.view';
|
||||
import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
import { ALL_RECIPIENTS_VIEW_ID } from '../views/all-recipients.view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: 'c1a2b3c4-0002-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineObject, FieldType } from '@/sdk';
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export const POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER =
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e';
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineObject, FieldType } from '@/sdk';
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
enum PostCardStatus {
|
||||
DRAFT = 'DRAFT',
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineObject, FieldType } from '@/sdk';
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export const RECIPIENT_UNIVERSAL_IDENTIFIER =
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { PermissionFlag, defineRole } from '@/sdk';
|
||||
import { PermissionFlag, defineRole } from 'twenty-sdk';
|
||||
import {
|
||||
CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
POST_CARD_UNIVERSAL_IDENTIFIER,
|
||||
} from '@/cli/__tests__/apps/rich-app/src/objects/post-card.object';
|
||||
} from '../objects/post-card.object';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '@/sdk';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const RootComponent = () => {
|
||||
return (
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineLogicFunction } from '@/sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const rootHandler = () => {
|
||||
return 'root-function-result';
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineObject, FieldType } from '@/sdk';
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: 'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineRole } from '@/sdk';
|
||||
import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: 'c0c1c2c3-c4c5-4000-8000-000000000001',
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user