Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ccdaa31273 | |||
| f4f7ccd0e1 |
@@ -3,13 +3,12 @@ description: >
|
||||
Starts a full Twenty instance (server, worker, database, redis) using Docker
|
||||
Compose. The server is available at http://localhost:3000 for subsequent steps
|
||||
in the caller's job.
|
||||
Accepts "latest" (pulls the latest Docker Hub image, checks out main) or a
|
||||
semver tag (e.g., v0.40.0).
|
||||
Pulls the specified semver image tag from Docker Hub.
|
||||
Designed to be consumed from external repositories (e.g., twenty-app).
|
||||
|
||||
inputs:
|
||||
twenty-version:
|
||||
description: 'Twenty Docker Hub image tag — either "latest" or a semver tag (e.g., v0.40.0).'
|
||||
description: 'Twenty Docker Hub image tag as semver (e.g., v0.40.0, v1.0.0).'
|
||||
required: true
|
||||
twenty-repository:
|
||||
description: 'Twenty repository to checkout docker compose files from.'
|
||||
@@ -31,19 +30,12 @@ outputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Resolve version
|
||||
id: resolve
|
||||
- name: Validate version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${{ inputs.twenty-version }}"
|
||||
if [ "$VERSION" = "latest" ]; then
|
||||
echo "docker-tag=latest" >> "$GITHUB_OUTPUT"
|
||||
echo "git-ref=main" >> "$GITHUB_OUTPUT"
|
||||
elif echo "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "docker-tag=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "git-ref=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::error::twenty-version must be \"latest\" or a semver tag (e.g., v0.40.0). Got: '$VERSION'"
|
||||
if ! echo "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "::error::twenty-version must be a semver tag (e.g., v0.40.0). Got: '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -51,7 +43,7 @@ runs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ inputs.twenty-repository }}
|
||||
ref: ${{ steps.resolve.outputs.git-ref }}
|
||||
ref: ${{ inputs.twenty-version }}
|
||||
token: ${{ inputs.github-token }}
|
||||
sparse-checkout: |
|
||||
packages/twenty-docker
|
||||
@@ -64,7 +56,7 @@ runs:
|
||||
run: |
|
||||
cp .env.example .env
|
||||
echo "" >> .env
|
||||
echo "TAG=${{ steps.resolve.outputs.docker-tag }}" >> .env
|
||||
echo "TAG=${{ inputs.twenty-version }}" >> .env
|
||||
echo "APP_SECRET=replace_me_with_a_random_string" >> .env
|
||||
echo "SERVER_URL=http://localhost:3000" >> .env
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@ packages:
|
||||
'twenty-sdk':
|
||||
access: $all
|
||||
publish: $all
|
||||
'twenty-client-sdk':
|
||||
access: $all
|
||||
publish: $all
|
||||
'create-twenty-app':
|
||||
access: $all
|
||||
publish: $all
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
name: AI Catalog Sync
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * *' # Daily at 6 AM UTC
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
sync-catalog:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-shared
|
||||
|
||||
- name: Run catalog sync
|
||||
run: npx nx run twenty-server:ts-node-no-deps-transpile-only -- ./scripts/ai-sync-models-dev.ts
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if git diff --quiet packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.changes.outputs.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: sync AI model catalog from models.dev'
|
||||
title: 'chore: sync AI model catalog from models.dev'
|
||||
body: |
|
||||
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev).
|
||||
|
||||
This PR updates pricing, context windows, and model availability based on the latest data.
|
||||
New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically.
|
||||
Deprecated models are detected based on cost-efficiency within the same model family.
|
||||
|
||||
**Please review before merging** — verify no critical models were incorrectly deprecated.
|
||||
branch: chore/ai-catalog-sync
|
||||
base: main
|
||||
labels: ai, automated
|
||||
delete-branch: true
|
||||
|
||||
- name: Trigger automerge
|
||||
if: steps.changes.outputs.changed == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: automated-pr-ready
|
||||
@@ -35,10 +35,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -75,8 +77,6 @@ jobs:
|
||||
run: |
|
||||
echo "Attempting to merge main into current branch..."
|
||||
|
||||
git config user.email "ci@twenty.com"
|
||||
git config user.name "CI"
|
||||
git fetch origin main
|
||||
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
@@ -90,8 +90,8 @@ jobs:
|
||||
echo "❌ Merge failed due to conflicts"
|
||||
echo "⚠️ Falling back to comparing current branch against main without merge"
|
||||
|
||||
# Abort the failed merge (may not exist if merge never started)
|
||||
git merge --abort 2>/dev/null || true
|
||||
# Abort the failed merge
|
||||
git merge --abort
|
||||
|
||||
echo "merged=false" >> $GITHUB_OUTPUT
|
||||
echo "BRANCH_STATE=conflicts" >> $GITHUB_ENV
|
||||
@@ -143,6 +143,7 @@ jobs:
|
||||
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
|
||||
|
||||
npx nx run twenty-server:database:init:prod
|
||||
npx nx run twenty-server:database:migrate:prod
|
||||
|
||||
- name: Seed current branch database with test data
|
||||
run: |
|
||||
@@ -295,6 +296,7 @@ jobs:
|
||||
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
|
||||
|
||||
npx nx run twenty-server:database:init:prod
|
||||
npx nx run twenty-server:database:migrate:prod
|
||||
|
||||
- name: Seed main branch database with test data
|
||||
run: |
|
||||
@@ -393,30 +395,12 @@ jobs:
|
||||
# Clean up temp directory
|
||||
rm -rf /tmp/current-branch-files
|
||||
|
||||
- name: Validate downloaded schema files
|
||||
id: validate-schemas
|
||||
run: |
|
||||
valid=true
|
||||
|
||||
for file in main-schema-introspection.json current-schema-introspection.json \
|
||||
main-metadata-schema-introspection.json current-metadata-schema-introspection.json \
|
||||
main-rest-api.json current-rest-api.json \
|
||||
main-rest-metadata-api.json current-rest-metadata-api.json; do
|
||||
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
|
||||
echo "::warning::Invalid or missing schema file: $file"
|
||||
valid=false
|
||||
fi
|
||||
done
|
||||
|
||||
echo "valid=$valid" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install OpenAPI Diff Tool
|
||||
run: |
|
||||
# Using the Java-based OpenAPITools/openapi-diff via Docker
|
||||
echo "Using OpenAPITools/openapi-diff via Docker"
|
||||
|
||||
- name: Generate GraphQL Schema Diff Reports
|
||||
if: steps.validate-schemas.outputs.valid == 'true'
|
||||
run: |
|
||||
echo "=== INSTALLING GRAPHQL INSPECTOR CLI ==="
|
||||
npm install -g @graphql-inspector/cli
|
||||
@@ -427,6 +411,7 @@ jobs:
|
||||
echo "Checking GraphQL schema for changes..."
|
||||
if graphql-inspector diff main-schema-introspection.json current-schema-introspection.json >/dev/null 2>&1; then
|
||||
echo "✅ No changes in GraphQL schema"
|
||||
# Don't create a diff file for no changes
|
||||
else
|
||||
echo "⚠️ Changes detected in GraphQL schema, generating report..."
|
||||
echo "# GraphQL Schema Changes" > graphql-schema-diff.md
|
||||
@@ -444,6 +429,7 @@ jobs:
|
||||
echo "Checking GraphQL metadata schema for changes..."
|
||||
if graphql-inspector diff main-metadata-schema-introspection.json current-metadata-schema-introspection.json >/dev/null 2>&1; then
|
||||
echo "✅ No changes in GraphQL metadata schema"
|
||||
# Don't create a diff file for no changes
|
||||
else
|
||||
echo "⚠️ Changes detected in GraphQL metadata schema, generating report..."
|
||||
echo "# GraphQL Metadata Schema Changes" > graphql-metadata-diff.md
|
||||
@@ -462,7 +448,6 @@ jobs:
|
||||
ls -la *-diff.md 2>/dev/null || echo "No diff files generated (no changes detected)"
|
||||
|
||||
- name: Check REST API Breaking Changes
|
||||
if: steps.validate-schemas.outputs.valid == 'true'
|
||||
run: |
|
||||
echo "=== CHECKING REST API FOR BREAKING CHANGES ==="
|
||||
|
||||
@@ -531,7 +516,6 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check REST Metadata API Breaking Changes
|
||||
if: steps.validate-schemas.outputs.valid == 'true'
|
||||
run: |
|
||||
echo "=== CHECKING REST METADATA API FOR BREAKING CHANGES ==="
|
||||
|
||||
|
||||
@@ -21,12 +21,10 @@ jobs:
|
||||
files: |
|
||||
packages/create-twenty-app/**
|
||||
packages/twenty-sdk/**
|
||||
packages/twenty-client-sdk/**
|
||||
packages/twenty-shared/**
|
||||
packages/twenty-server/**
|
||||
!packages/create-twenty-app/package.json
|
||||
!packages/twenty-sdk/package.json
|
||||
!packages/twenty-client-sdk/package.json
|
||||
!packages/twenty-shared/package.json
|
||||
!packages/twenty-server/package.json
|
||||
create-app-e2e:
|
||||
@@ -36,10 +34,12 @@ jobs:
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -51,8 +51,6 @@ jobs:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
PUBLISHABLE_PACKAGES: twenty-client-sdk twenty-sdk create-twenty-app
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
@@ -66,13 +64,12 @@ jobs:
|
||||
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 $PUBLISHABLE_PACKAGES --releaseVersion=$CI_VERSION
|
||||
npx nx run-many -t set-local-version -p twenty-sdk create-twenty-app --releaseVersion=$CI_VERSION
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
for pkg in $PUBLISHABLE_PACKAGES; do
|
||||
npx nx build $pkg
|
||||
done
|
||||
npx nx build twenty-sdk
|
||||
npx nx build create-twenty-app
|
||||
|
||||
- name: Install and start Verdaccio
|
||||
run: |
|
||||
@@ -89,13 +86,11 @@ jobs:
|
||||
|
||||
- name: Publish packages to local registry
|
||||
run: |
|
||||
yarn config set npmRegistryServer http://localhost:4873
|
||||
yarn config set unsafeHttpWhitelist --json '["localhost"]'
|
||||
yarn config set npmAuthToken ci-auth-token
|
||||
npm set //localhost:4873/:_authToken "ci-auth-token"
|
||||
|
||||
for pkg in $PUBLISHABLE_PACKAGES; do
|
||||
for pkg in twenty-sdk create-twenty-app; do
|
||||
cd packages/$pkg
|
||||
yarn npm publish --tag ci
|
||||
npm publish --registry http://localhost:4873 --tag ci
|
||||
cd ../..
|
||||
done
|
||||
|
||||
@@ -156,17 +151,13 @@ jobs:
|
||||
SEED_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik'
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty remote add --api-key $SEED_API_KEY --api-url http://localhost:3000
|
||||
npx --no-install twenty remote add --token $SEED_API_KEY --url http://localhost:3000
|
||||
|
||||
- name: Deploy scaffolded app
|
||||
- name: Build scaffolded app
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty deploy
|
||||
|
||||
- name: Install scaffolded app
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty install
|
||||
npx --no-install twenty build
|
||||
test -d .twenty/output
|
||||
|
||||
- name: Execute hello-world logic function
|
||||
run: |
|
||||
@@ -175,13 +166,6 @@ jobs:
|
||||
echo "$EXEC_OUTPUT"
|
||||
echo "$EXEC_OUTPUT" | grep -q "Hello, World!"
|
||||
|
||||
- name: Execute create-hello-world-company logic function
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
EXEC_OUTPUT=$(npx --no-install twenty exec --functionName create-hello-world-company)
|
||||
echo "$EXEC_OUTPUT"
|
||||
echo "$EXEC_OUTPUT" | grep -q 'Created company.*Hello World.*with id'
|
||||
|
||||
- name: Run scaffolded app integration test
|
||||
env:
|
||||
TWENTY_API_URL: http://localhost:3000
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
name: CI Front Component Renderer
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
if: github.event_name != 'merge_group'
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-front-component-renderer/**
|
||||
packages/twenty-sdk/**
|
||||
packages/twenty-shared/**
|
||||
!packages/twenty-sdk/package.json
|
||||
renderer-task:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [build, typecheck, lint]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run ${{ matrix.task }}
|
||||
run: npx nx ${{ matrix.task }} twenty-front-component-renderer
|
||||
renderer-sb-build:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build storybook
|
||||
run: npx nx storybook:build twenty-front-component-renderer
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-front-component-renderer
|
||||
path: packages/twenty-front-component-renderer/storybook-static
|
||||
retention-days: 1
|
||||
renderer-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
needs: renderer-sb-build
|
||||
env:
|
||||
STORYBOOK_URL: http://localhost:6008
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-front-component-renderer
|
||||
path: packages/twenty-front-component-renderer/storybook-static
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd packages/twenty-front-component-renderer
|
||||
npx playwright install
|
||||
- name: Serve storybook & run tests
|
||||
run: |
|
||||
npx http-server packages/twenty-front-component-renderer/storybook-static --port 6008 --silent &
|
||||
timeout 30 bash -c 'until curl -sf http://localhost:6008 > /dev/null 2>&1; do sleep 1; done'
|
||||
npx nx storybook:test twenty-front-component-renderer
|
||||
ci-front-component-renderer-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
renderer-task,
|
||||
renderer-sb-build,
|
||||
renderer-sb-test,
|
||||
]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
@@ -25,7 +25,6 @@ jobs:
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-front/**
|
||||
packages/twenty-front-component-renderer/**
|
||||
packages/twenty-ui/**
|
||||
packages/twenty-shared/**
|
||||
packages/twenty-sdk/**
|
||||
@@ -94,7 +93,7 @@ jobs:
|
||||
run: |
|
||||
npx nx build twenty-shared
|
||||
npx nx build twenty-ui
|
||||
npx nx build twenty-front-component-renderer
|
||||
npx nx build twenty-sdk
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -25,10 +25,12 @@ jobs:
|
||||
NODE_OPTIONS: "--max-old-space-size=10240"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: "true"
|
||||
SPILO_PROVIDER: "local"
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test:unit, test:integration]
|
||||
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
@@ -41,6 +41,9 @@ jobs:
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Install Playwright
|
||||
if: contains(matrix.task, 'storybook')
|
||||
run: npx playwright install chromium
|
||||
- name: Run ${{ matrix.task }} task
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
@@ -53,10 +56,12 @@ jobs:
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
packages/twenty-server/**
|
||||
packages/twenty-front/src/generated/**
|
||||
packages/twenty-front/src/generated-metadata/**
|
||||
packages/twenty-client-sdk/**
|
||||
packages/twenty-sdk/src/clients/generated/metadata/**
|
||||
packages/twenty-emails/**
|
||||
packages/twenty-shared/**
|
||||
|
||||
@@ -83,10 +83,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
@@ -120,6 +122,7 @@ jobs:
|
||||
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:init:prod
|
||||
npx nx run twenty-server:database:migrate:prod
|
||||
- name: Worker / Run
|
||||
run: |
|
||||
timeout 30s npx nx run twenty-server:worker || exit_code=$?
|
||||
@@ -174,14 +177,14 @@ jobs:
|
||||
HAS_ERRORS=true
|
||||
fi
|
||||
|
||||
npx nx run twenty-client-sdk:generate-metadata-client
|
||||
npx nx run twenty-sdk:generate-metadata-client
|
||||
|
||||
if ! git diff --quiet -- packages/twenty-client-sdk/src/metadata/generated; then
|
||||
echo "::error::SDK metadata client changes detected. Please run 'npx nx run twenty-client-sdk:generate-metadata-client' and commit the changes."
|
||||
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-client-sdk/src/metadata/generated
|
||||
git diff -- packages/twenty-sdk/src/clients/generated/metadata
|
||||
echo "==================================================="
|
||||
echo ""
|
||||
HAS_ERRORS=true
|
||||
@@ -223,10 +226,12 @@ jobs:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: CI Docker
|
||||
name: CI Docker Compose
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
files: |
|
||||
packages/twenty-docker/**
|
||||
docker-compose.yml
|
||||
test-compose:
|
||||
test:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
@@ -27,18 +27,13 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
- name: Run compose
|
||||
run: |
|
||||
echo "Patching docker-compose.yml..."
|
||||
# change image to localbuild using yq
|
||||
yq eval 'del(.services.server.image)' -i docker-compose.yml
|
||||
yq eval '.services.server.build.context = "../../"' -i docker-compose.yml
|
||||
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i docker-compose.yml
|
||||
yq eval '.services.server.build.target = "twenty"' -i docker-compose.yml
|
||||
yq eval '.services.server.restart = "no"' -i docker-compose.yml
|
||||
|
||||
echo "Setting up .env file..."
|
||||
@@ -94,69 +89,11 @@ jobs:
|
||||
echo "Still waiting for server... (${count}/300s)"
|
||||
done
|
||||
working-directory: ./packages/twenty-docker/
|
||||
test-app-dev:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
- name: Create frontend placeholder
|
||||
run: |
|
||||
mkdir -p packages/twenty-front/build
|
||||
echo '<html><body>CI placeholder</body></html>' > packages/twenty-front/build/index.html
|
||||
- name: Build app-dev image
|
||||
run: |
|
||||
docker build \
|
||||
--target twenty-app-dev \
|
||||
-f packages/twenty-docker/twenty/Dockerfile \
|
||||
-t twenty-app-dev-ci \
|
||||
.
|
||||
- name: Start container
|
||||
run: |
|
||||
docker run -d --name twenty-app-dev \
|
||||
-p 2020:2020 \
|
||||
twenty-app-dev-ci
|
||||
docker logs twenty-app-dev -f &
|
||||
- name: Wait for server health
|
||||
run: |
|
||||
echo "Waiting for twenty-app-dev to become healthy..."
|
||||
count=0
|
||||
while true; do
|
||||
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2020/healthz 2>/dev/null || echo "000")
|
||||
if [ "$status" = "200" ]; then
|
||||
echo "Server is healthy!"
|
||||
curl -s http://localhost:2020/healthz
|
||||
break
|
||||
fi
|
||||
|
||||
container_status=$(docker inspect --format='{{.State.Status}}' twenty-app-dev 2>/dev/null || echo "unknown")
|
||||
if [ "$container_status" = "exited" ]; then
|
||||
echo "Container exited unexpectedly"
|
||||
docker logs twenty-app-dev
|
||||
exit 1
|
||||
fi
|
||||
|
||||
count=$((count+1))
|
||||
if [ $count -gt 300 ]; then
|
||||
echo "Server did not become healthy within 5 minutes"
|
||||
docker logs twenty-app-dev
|
||||
exit 1
|
||||
fi
|
||||
echo "Still waiting... (${count}/300s) [HTTP ${status}]"
|
||||
sleep 1
|
||||
done
|
||||
ci-test-docker-status-check:
|
||||
ci-test-docker-compose-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, test-compose, test-app-dev]
|
||||
needs: [changed-files-check, test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
name: CI UI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
if: github.event_name != 'merge_group'
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
package.json
|
||||
yarn.lock
|
||||
packages/twenty-ui/**
|
||||
packages/twenty-shared/**
|
||||
ui-task:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Run ${{ matrix.task }}
|
||||
run: npx nx ${{ matrix.task }} twenty-ui
|
||||
ui-sb-build:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build storybook
|
||||
run: npx nx storybook:build twenty-ui
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-ui
|
||||
path: packages/twenty-ui/storybook-static
|
||||
retention-days: 1
|
||||
ui-sb-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
needs: ui-sb-build
|
||||
env:
|
||||
STORYBOOK_URL: http://localhost:6007
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build dependencies
|
||||
run: npx nx build twenty-shared
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-twenty-ui
|
||||
path: packages/twenty-ui/storybook-static
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd packages/twenty-ui
|
||||
npx playwright install
|
||||
- name: Serve storybook & run tests
|
||||
run: |
|
||||
npx http-server packages/twenty-ui/storybook-static --port 6007 --silent &
|
||||
timeout 30 bash -c 'until curl -sf http://localhost:6007 > /dev/null 2>&1; do sleep 1; done'
|
||||
npx nx storybook:test twenty-ui
|
||||
ci-ui-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
ui-task,
|
||||
ui-sb-build,
|
||||
ui-sb-test,
|
||||
]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
@@ -26,10 +26,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
|
||||
@@ -29,10 +29,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
PGUSER_SUPERUSER: postgres
|
||||
PGPASSWORD_SUPERUSER: postgres
|
||||
ALLOW_NOSSL: 'true'
|
||||
SPILO_PROVIDER: 'local'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
|
||||
@@ -150,11 +150,3 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: github.event_name != 'pull_request' && steps.check_changes.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
|
||||
@@ -138,11 +138,3 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: steps.compile_translations.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
|
||||
@@ -102,11 +102,3 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger i18n automerge
|
||||
if: steps.check_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
|
||||
repository: twentyhq/twenty-infra
|
||||
event-type: i18n-pr-ready
|
||||
|
||||
@@ -17,12 +17,6 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.client_payload.pr_head_sha }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Run compose setup
|
||||
run: |
|
||||
echo "Patching docker-compose.yml..."
|
||||
@@ -30,12 +24,10 @@ jobs:
|
||||
yq eval 'del(.services.server.image)' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.server.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.server.build.target = "twenty"' -i packages/twenty-docker/docker-compose.yml
|
||||
|
||||
yq eval 'del(.services.worker.image)' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.worker.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.worker.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.worker.build.target = "twenty"' -i packages/twenty-docker/docker-compose.yml
|
||||
|
||||
echo "Adding SIGN_IN_PREFILLED environment variable to server service..."
|
||||
yq eval '.services.server.environment.SIGN_IN_PREFILLED = "${SIGN_IN_PREFILLED}"' -i packages/twenty-docker/docker-compose.yml
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
name: Visual Regression Dispatch
|
||||
|
||||
# Uses workflow_run to dispatch visual regression to ci-privileged.
|
||||
# This runs in the context of the base repo (not the fork), so it has
|
||||
# access to secrets — making it work for external contributor PRs.
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['CI Front', 'CI UI']
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
if: >-
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Determine project and artifact name
|
||||
id: project
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const workflowName = context.payload.workflow_run.name;
|
||||
if (workflowName === 'CI Front') {
|
||||
core.setOutput('project', 'twenty-front');
|
||||
core.setOutput('artifact_name', 'storybook-static');
|
||||
core.setOutput('tarball_name', 'storybook-twenty-front-tarball');
|
||||
core.setOutput('tarball_file', 'storybook-twenty-front.tar.gz');
|
||||
} else if (workflowName === 'CI UI') {
|
||||
core.setOutput('project', 'twenty-ui');
|
||||
core.setOutput('artifact_name', 'storybook-twenty-ui');
|
||||
core.setOutput('tarball_name', 'storybook-twenty-ui-tarball');
|
||||
core.setOutput('tarball_file', 'storybook-twenty-ui.tar.gz');
|
||||
} else {
|
||||
core.setFailed(`Unexpected workflow: ${workflowName}`);
|
||||
}
|
||||
|
||||
- name: Check if storybook artifact exists
|
||||
id: check-artifact
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const artifactName = '${{ steps.project.outputs.artifact_name }}';
|
||||
const runId = context.payload.workflow_run.id;
|
||||
|
||||
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: runId,
|
||||
});
|
||||
|
||||
const found = artifacts.artifacts.some(a => a.name === artifactName);
|
||||
core.setOutput('exists', found ? 'true' : 'false');
|
||||
|
||||
if (!found) {
|
||||
core.info(`Artifact "${artifactName}" not found in run ${runId} — storybook build was likely skipped`);
|
||||
}
|
||||
|
||||
- name: Get PR number
|
||||
if: steps.check-artifact.outputs.exists == 'true'
|
||||
id: pr-info
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const headBranch = context.payload.workflow_run.head_branch;
|
||||
const headRepo = context.payload.workflow_run.head_repository;
|
||||
|
||||
// workflow_run.pull_requests is empty for fork PRs,
|
||||
// so fall back to searching by head label (owner:branch)
|
||||
let pullRequests = context.payload.workflow_run.pull_requests;
|
||||
let prNumber;
|
||||
|
||||
if (pullRequests && pullRequests.length > 0) {
|
||||
prNumber = pullRequests[0].number;
|
||||
} else {
|
||||
const headLabel = `${headRepo.owner.login}:${headBranch}`;
|
||||
core.info(`pull_requests is empty (likely a fork PR), searching by head label: ${headLabel}`);
|
||||
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
head: headLabel,
|
||||
per_page: 1,
|
||||
});
|
||||
|
||||
if (prs.length > 0) {
|
||||
prNumber = prs[0].number;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prNumber) {
|
||||
core.info('No pull request found for this workflow run — skipping');
|
||||
core.setOutput('has_pr', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput('pr_number', prNumber);
|
||||
core.setOutput('has_pr', 'true');
|
||||
core.info(`PR #${prNumber}`);
|
||||
|
||||
- name: Download storybook artifact from triggering run
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.project.outputs.artifact_name }}
|
||||
path: storybook-static
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Package storybook
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
run: tar -czf /tmp/${{ steps.project.outputs.tarball_file }} -C storybook-static .
|
||||
|
||||
- name: Upload storybook tarball
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.project.outputs.tarball_name }}
|
||||
path: /tmp/${{ steps.project.outputs.tarball_file }}
|
||||
retention-days: 1
|
||||
|
||||
- name: Dispatch to ci-privileged
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
||||
repository: twentyhq/ci-privileged
|
||||
event-type: visual-regression
|
||||
client-payload: >-
|
||||
{
|
||||
"pr_number": "${{ steps.pr-info.outputs.pr_number }}",
|
||||
"run_id": "${{ github.run_id }}",
|
||||
"repo": "${{ github.repository }}",
|
||||
"project": "${{ steps.project.outputs.project }}",
|
||||
"branch": "${{ github.event.workflow_run.head_branch }}",
|
||||
"commit": "${{ github.event.workflow_run.head_sha }}"
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
**/**/.env
|
||||
.DS_Store
|
||||
/.idea
|
||||
.claude/settings.json
|
||||
**/**/node_modules/
|
||||
.cache
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"mcpServers": {
|
||||
"postgres": {
|
||||
"type": "stdio",
|
||||
"command": "bash",
|
||||
"args": ["-c", "source packages/twenty-server/.env && npx -y @modelcontextprotocol/server-postgres \"$PG_DATABASE_URL\""],
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-postgres", "${PG_DATABASE_URL}"],
|
||||
"env": {}
|
||||
},
|
||||
"playwright": {
|
||||
|
||||
Vendored
-940
File diff suppressed because one or more lines are too long
+942
File diff suppressed because one or more lines are too long
+1
-1
@@ -6,4 +6,4 @@ enableInlineHunks: true
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.13.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
|
||||
|
||||
@@ -80,17 +80,6 @@ npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/mi
|
||||
npx nx run twenty-server:command workspace:sync-metadata
|
||||
```
|
||||
|
||||
### Database Inspection (Postgres MCP)
|
||||
|
||||
A read-only Postgres MCP server is configured in `.mcp.json`. Use it to:
|
||||
- Inspect workspace data, metadata, and object definitions while developing
|
||||
- Verify migration results (columns, types, constraints) after running migrations
|
||||
- Explore the multi-tenant schema structure (core, metadata, workspace-specific schemas)
|
||||
- Debug issues by querying raw data to confirm whether a bug is frontend, backend, or data-level
|
||||
- Inspect metadata tables to debug GraphQL schema generation or `workspace:sync-metadata` issues
|
||||
|
||||
This server is read-only — for write operations (reset, migrations, sync), use the CLI commands above.
|
||||
|
||||
### GraphQL
|
||||
```bash
|
||||
# Generate GraphQL types (run after schema changes)
|
||||
|
||||
+8
-13
@@ -82,11 +82,11 @@
|
||||
"@sentry/types": "^8",
|
||||
"@storybook-community/storybook-addon-cookie": "^5.0.0",
|
||||
"@storybook/addon-coverage": "^3.0.0",
|
||||
"@storybook/addon-docs": "^10.3.3",
|
||||
"@storybook/addon-links": "^10.3.3",
|
||||
"@storybook/addon-vitest": "^10.3.3",
|
||||
"@storybook/addon-docs": "^10.2.13",
|
||||
"@storybook/addon-links": "^10.2.13",
|
||||
"@storybook/addon-vitest": "^10.2.13",
|
||||
"@storybook/icons": "^2.0.1",
|
||||
"@storybook/react-vite": "^10.3.3",
|
||||
"@storybook/react-vite": "^10.2.13",
|
||||
"@storybook/test-runner": "^0.24.2",
|
||||
"@swc-node/register": "^1.11.1",
|
||||
"@swc/cli": "^0.7.10",
|
||||
@@ -154,9 +154,9 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"source-map-support": "^0.5.20",
|
||||
"storybook": "^10.3.3",
|
||||
"storybook": "^10.2.13",
|
||||
"storybook-addon-mock-date": "2.0.0",
|
||||
"storybook-addon-pseudo-states": "^10.3.3",
|
||||
"storybook-addon-pseudo-states": "^10.2.13",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.2.3",
|
||||
@@ -175,12 +175,11 @@
|
||||
},
|
||||
"license": "AGPL-3.0",
|
||||
"name": "twenty",
|
||||
"packageManager": "yarn@4.13.0",
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"resolutions": {
|
||||
"graphql": "16.8.1",
|
||||
"type-fest": "4.10.1",
|
||||
"typescript": "5.9.2",
|
||||
"nodemailer": "8.0.4",
|
||||
"graphql-redis-subscriptions/ioredis": "^5.6.0",
|
||||
"@lingui/core": "5.1.2",
|
||||
"@types/qs": "6.9.16",
|
||||
@@ -204,18 +203,14 @@
|
||||
"packages/twenty-utils",
|
||||
"packages/twenty-zapier",
|
||||
"packages/twenty-website",
|
||||
"packages/twenty-website-new",
|
||||
"packages/twenty-docs",
|
||||
"packages/twenty-e2e-testing",
|
||||
"packages/twenty-shared",
|
||||
"packages/twenty-sdk",
|
||||
"packages/twenty-front-component-renderer",
|
||||
"packages/twenty-client-sdk",
|
||||
"packages/twenty-apps",
|
||||
"packages/twenty-cli",
|
||||
"packages/create-twenty-app",
|
||||
"packages/twenty-oxlint-rules",
|
||||
"packages/twenty-companion"
|
||||
"packages/twenty-oxlint-rules"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
@@ -12,49 +12,163 @@
|
||||
|
||||
</div>
|
||||
|
||||
The official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). Sets up a ready-to-run project with [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
|
||||
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a ready‑to‑run project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
|
||||
|
||||
- Zero‑config project bootstrap
|
||||
- Preconfigured scripts for auth, dev mode (watch & sync), uninstall, and function management
|
||||
- Strong TypeScript support and typed client generation
|
||||
|
||||
## Documentation
|
||||
|
||||
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 24+ (recommended) and Yarn 4
|
||||
- Docker (for the local Twenty dev server)
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# Scaffold a new app — the CLI will offer to start a local Twenty server
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
|
||||
# The scaffolder can automatically:
|
||||
# 1. Start a local Twenty server (Docker)
|
||||
# 2. Open the browser to log in (tim@apple.dev / tim@apple.dev)
|
||||
# 3. Authenticate your app via OAuth
|
||||
|
||||
# Or do it manually:
|
||||
yarn twenty server start # Start local Twenty server
|
||||
yarn twenty remote add --local # Authenticate via OAuth
|
||||
|
||||
# Start dev mode: watches, builds, and syncs local changes to your workspace
|
||||
yarn twenty dev
|
||||
|
||||
# Watch your application's function logs
|
||||
yarn twenty logs
|
||||
|
||||
# Execute a function with a JSON payload
|
||||
yarn twenty exec -n my-function -p '{"key": "value"}'
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty exec --preInstall
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty exec --postInstall
|
||||
|
||||
# Build the app for distribution
|
||||
yarn twenty build
|
||||
|
||||
# Publish the app to npm or directly to a Twenty server
|
||||
yarn twenty publish
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty uninstall
|
||||
```
|
||||
|
||||
The scaffolder will:
|
||||
|
||||
1. Create a new project with TypeScript, linting, and a preconfigured `twenty` CLI
|
||||
2. Optionally start a local Twenty server (Docker)
|
||||
3. Open the browser for OAuth authentication
|
||||
4. Scaffold example entities and an integration test
|
||||
|
||||
## Scaffolding modes
|
||||
|
||||
| Flag | Behavior |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| `--minimal` | **(default)** Creates only core files (`application-config.ts`, `default-role.ts`, pre/post-install functions) |
|
||||
| `--exhaustive` | Creates all example entities |
|
||||
Control which example files are included when creating a new app:
|
||||
|
||||
Other flags:
|
||||
| Flag | Behavior |
|
||||
| ------------------ | ----------------------------------------------------------------------- |
|
||||
| `-e, --exhaustive` | **(default)** Creates all example files |
|
||||
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
|
||||
|
||||
- `--name <name>` — set the app name (skips the prompt)
|
||||
- `--display-name <displayName>` — set the display name (skips the prompt)
|
||||
- `--description <description>` — set the description (skips the prompt)
|
||||
- `--skip-local-instance` — skip the local server setup prompt
|
||||
```bash
|
||||
# Default: all examples included
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
## Documentation
|
||||
# Minimal: only core files
|
||||
npx create-twenty-app@latest my-app -m
|
||||
```
|
||||
|
||||
Full documentation is available at **[docs.twenty.com/developers/extend/apps](https://docs.twenty.com/developers/extend/apps/getting-started)**:
|
||||
## What gets scaffolded
|
||||
|
||||
- [Getting Started](https://docs.twenty.com/developers/extend/apps/getting-started) — step-by-step setup, project structure, server management, CI
|
||||
- [Building Apps](https://docs.twenty.com/developers/extend/apps/building) — entity definitions, API clients, testing
|
||||
- [Publishing](https://docs.twenty.com/developers/extend/apps/publishing) — deploy, npm publish, marketplace
|
||||
**Core files (always created):**
|
||||
|
||||
- `application-config.ts` — Application metadata configuration
|
||||
- `roles/default-role.ts` — Default role for logic functions
|
||||
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
|
||||
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
|
||||
- TypeScript configuration, Oxlint, package.json, .gitignore
|
||||
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
|
||||
|
||||
**Example files (controlled by scaffolding mode):**
|
||||
|
||||
- `objects/example-object.ts` — Example custom object with a text field
|
||||
- `fields/example-field.ts` — Example standalone field extending the example object
|
||||
- `logic-functions/hello-world.ts` — Example logic function with HTTP trigger
|
||||
- `front-components/hello-world.tsx` — Example front component
|
||||
- `views/example-view.ts` — Example saved view for the example object
|
||||
- `navigation-menu-items/example-navigation-menu-item.ts` — Example sidebar navigation link
|
||||
- `skills/example-skill.ts` — Example AI agent skill definition
|
||||
- `__tests__/app-install.integration-test.ts` — Integration test that builds, installs, and verifies the app (includes `vitest.config.ts`, `tsconfig.spec.json`, and a setup file)
|
||||
|
||||
## Local server
|
||||
|
||||
The scaffolder can start a local Twenty dev server for you (all-in-one Docker image with PostgreSQL, Redis, server, and worker). You can also manage it manually:
|
||||
|
||||
```bash
|
||||
yarn twenty server start # Start (pulls image if needed)
|
||||
yarn twenty server status # Check if it's healthy
|
||||
yarn twenty server logs # Stream logs
|
||||
yarn twenty server stop # Stop (data is preserved)
|
||||
yarn twenty server reset # Wipe all data and start fresh
|
||||
```
|
||||
|
||||
The server is pre-seeded with a workspace and user (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
## Next steps
|
||||
|
||||
- Run `yarn twenty help` to see all available commands.
|
||||
- Use `yarn twenty remote add --local` to authenticate with your Twenty workspace via OAuth.
|
||||
- Explore the generated project and add your first entity with `yarn twenty add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
|
||||
- Use `yarn twenty dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
|
||||
- `CoreApiClient` (for workspace data via `/graphql`) is auto-generated by `yarn twenty dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/clients'`.
|
||||
|
||||
## Build and publish your application
|
||||
|
||||
Once your app is ready, build and publish it using the CLI:
|
||||
|
||||
```bash
|
||||
# Build the app (output goes to .twenty/output/)
|
||||
yarn twenty build
|
||||
|
||||
# Build and create a tarball (.tgz) for distribution
|
||||
yarn twenty build --tarball
|
||||
|
||||
# Publish to npm (requires npm login)
|
||||
yarn twenty publish
|
||||
|
||||
# Publish with a dist-tag (e.g. beta, next)
|
||||
yarn twenty publish --tag beta
|
||||
|
||||
# Deploy directly to a Twenty server (builds, uploads, and installs in one step)
|
||||
yarn twenty deploy
|
||||
```
|
||||
|
||||
### Publish to the Twenty marketplace
|
||||
|
||||
You can also contribute your application to the curated marketplace:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
cd twenty
|
||||
git checkout -b feature/my-awesome-app
|
||||
```
|
||||
|
||||
- Copy your app folder into `twenty/packages/twenty-apps`.
|
||||
- Commit your changes and open a pull request on https://github.com/twentyhq/twenty
|
||||
|
||||
Our team reviews contributions for quality, security, and reusability before merging.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Server not starting: check Docker is running (`docker info`), then try `yarn twenty server logs`.
|
||||
- Auth not working: make sure you are logged in to Twenty in the browser, then run `yarn twenty remote add`.
|
||||
- Auth not working: make sure you're logged in to Twenty in the browser first, then run `yarn twenty remote add --local`.
|
||||
- Types not generated: ensure `yarn twenty dev` is running — it auto-generates the typed client.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.8.0-canary.8",
|
||||
"version": "0.8.0-canary.1",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
@@ -36,7 +36,6 @@
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"lodash.startcase": "^4.4.0",
|
||||
"twenty-sdk": "workspace:*",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -46,6 +45,7 @@
|
||||
"@types/lodash.kebabcase": "^4.1.7",
|
||||
"@types/lodash.startcase": "^4",
|
||||
"@types/node": "^20.0.0",
|
||||
"twenty-sdk": "workspace:*",
|
||||
"twenty-shared": "workspace:*",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.0.0",
|
||||
|
||||
@@ -13,10 +13,10 @@ const program = new Command(packageJson.name)
|
||||
'Output the current version of create-twenty-app.',
|
||||
)
|
||||
.argument('[directory]')
|
||||
.option('-e, --exhaustive', 'Create all example entities')
|
||||
.option('-e, --exhaustive', 'Create all example entities (default)')
|
||||
.option(
|
||||
'-m, --minimal',
|
||||
'Create only core entities (application-config and default-role) (default)',
|
||||
'Create only core entities (application-config and default-role)',
|
||||
)
|
||||
.option('-n, --name <name>', 'Application name (skips prompt)')
|
||||
.option(
|
||||
@@ -69,9 +69,7 @@ const program = new Command(packageJson.name)
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const mode: ScaffoldingMode = options?.exhaustive
|
||||
? 'exhaustive'
|
||||
: 'minimal';
|
||||
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
|
||||
|
||||
await new CreateAppCommand().execute({
|
||||
directory,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
|
||||
## UUID requirement
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { copyBaseApplicationProject } from '@/utils/app-template';
|
||||
import { convertToLabel } from '@/utils/convert-to-label';
|
||||
import { install } from '@/utils/install';
|
||||
import {
|
||||
type LocalInstanceResult,
|
||||
setupLocalInstance,
|
||||
} from '@/utils/setup-local-instance';
|
||||
import { tryGitInit } from '@/utils/try-git-init';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import { execSync } from 'node:child_process';
|
||||
import * as path from 'path';
|
||||
import { basename } from 'path';
|
||||
import {
|
||||
authLoginOAuth,
|
||||
detectLocalServer,
|
||||
serverStart,
|
||||
type ServerStartResult,
|
||||
} from 'twenty-sdk/cli';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
@@ -34,12 +32,12 @@ type CreateAppOptions = {
|
||||
|
||||
export class CreateAppCommand {
|
||||
async execute(options: CreateAppOptions = {}): Promise<void> {
|
||||
const { appName, appDisplayName, appDirectory, appDescription } =
|
||||
await this.getAppInfos(options);
|
||||
|
||||
try {
|
||||
const { appName, appDisplayName, appDirectory, appDescription } =
|
||||
await this.getAppInfos(options);
|
||||
|
||||
const exampleOptions = this.resolveExampleOptions(
|
||||
options.mode ?? 'minimal',
|
||||
options.mode ?? 'exhaustive',
|
||||
);
|
||||
|
||||
await this.validateDirectory(appDirectory);
|
||||
@@ -48,6 +46,7 @@ export class CreateAppCommand {
|
||||
|
||||
await fs.ensureDir(appDirectory);
|
||||
|
||||
console.log(chalk.gray(' Scaffolding project files...'));
|
||||
await copyBaseApplicationProject({
|
||||
appName,
|
||||
appDisplayName,
|
||||
@@ -56,33 +55,27 @@ export class CreateAppCommand {
|
||||
exampleOptions,
|
||||
});
|
||||
|
||||
console.log(chalk.gray(' Installing dependencies...'));
|
||||
await install(appDirectory);
|
||||
|
||||
console.log(chalk.gray(' Initializing git repository...'));
|
||||
await tryGitInit(appDirectory);
|
||||
|
||||
let serverResult: ServerStartResult | undefined;
|
||||
let localResult: LocalInstanceResult = { running: false };
|
||||
|
||||
if (!options.skipLocalInstance) {
|
||||
const shouldStartServer = await this.shouldStartServer();
|
||||
// Auto-detect a running server first
|
||||
localResult = await setupLocalInstance(appDirectory);
|
||||
|
||||
if (shouldStartServer) {
|
||||
const startResult = await serverStart({
|
||||
onProgress: (message: string) => console.log(chalk.gray(message)),
|
||||
});
|
||||
|
||||
if (startResult.success) {
|
||||
serverResult = startResult.data;
|
||||
await this.promptConnectToLocal(serverResult.url);
|
||||
} else {
|
||||
console.log(chalk.yellow(`\n${startResult.error.message}`));
|
||||
}
|
||||
if (localResult.running && localResult.serverUrl) {
|
||||
await this.connectToLocal(appDirectory, localResult.serverUrl);
|
||||
}
|
||||
}
|
||||
|
||||
this.logSuccess(appDirectory, serverResult);
|
||||
this.logSuccess(appDirectory, localResult);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red('\nCreate application failed:'),
|
||||
chalk.red('Initialization failed:'),
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -163,7 +156,7 @@ export class CreateAppCommand {
|
||||
includeExampleNavigationMenuItem: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleAgent: false,
|
||||
includeExampleIntegrationTest: true,
|
||||
includeExampleIntegrationTest: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -200,69 +193,29 @@ export class CreateAppCommand {
|
||||
appDirectory: string;
|
||||
appName: string;
|
||||
}): void {
|
||||
console.log(
|
||||
chalk.blue('\n', 'Creating Twenty Application\n'),
|
||||
chalk.gray(`- Directory: ${appDirectory}\n`, `- Name: ${appName}\n`),
|
||||
);
|
||||
console.log(chalk.blue('Creating Twenty Application'));
|
||||
console.log(chalk.gray(` Directory: ${appDirectory}`));
|
||||
console.log(chalk.gray(` Name: ${appName}`));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
private async shouldStartServer(): Promise<boolean> {
|
||||
const existingServerUrl = await detectLocalServer();
|
||||
|
||||
if (existingServerUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { startDocker } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'startDocker',
|
||||
message:
|
||||
'No running Twenty instance found. Would you like to start one using Docker?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
return startDocker;
|
||||
}
|
||||
|
||||
private async promptConnectToLocal(serverUrl: string): Promise<void> {
|
||||
const { shouldAuthenticate } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'shouldAuthenticate',
|
||||
message: `Would you like to authenticate to the local Twenty instance (${serverUrl})?`,
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!shouldAuthenticate) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'Authentication skipped. Run `yarn twenty remote add` manually.',
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private async connectToLocal(
|
||||
appDirectory: string,
|
||||
serverUrl: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const result = await authLoginOAuth({
|
||||
apiUrl: serverUrl,
|
||||
remote: 'local',
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Authentication failed. Run `yarn twenty remote add` manually.',
|
||||
),
|
||||
);
|
||||
}
|
||||
execSync(
|
||||
`npx nx run twenty-sdk:start -- remote add ${serverUrl} --as local`,
|
||||
{
|
||||
cwd: appDirectory,
|
||||
stdio: 'inherit',
|
||||
},
|
||||
);
|
||||
console.log(chalk.green('Authenticated with local Twenty instance.'));
|
||||
} catch {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Authentication failed. Run `yarn twenty remote add` manually.',
|
||||
'Authentication skipped. Run `npx nx run twenty-sdk:start -- remote add --local` manually.',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -270,23 +223,25 @@ export class CreateAppCommand {
|
||||
|
||||
private logSuccess(
|
||||
appDirectory: string,
|
||||
serverResult?: ServerStartResult,
|
||||
localResult: LocalInstanceResult,
|
||||
): void {
|
||||
const dirName = basename(appDirectory);
|
||||
const dirName = appDirectory.split('/').reverse()[0] ?? '';
|
||||
|
||||
console.log(chalk.blue('\nApplication created. Next steps:'));
|
||||
console.log(chalk.gray(`- cd ${dirName}`));
|
||||
console.log(chalk.green('Application created!'));
|
||||
console.log('');
|
||||
console.log(chalk.blue('Next steps:'));
|
||||
console.log(chalk.gray(` cd ${dirName}`));
|
||||
|
||||
if (!serverResult) {
|
||||
if (!localResult.running) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'- yarn twenty remote add # Authenticate with Twenty',
|
||||
' yarn twenty remote add --local # Authenticate with Twenty',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.gray('- yarn twenty dev # Start dev mode'),
|
||||
chalk.gray(' yarn twenty dev # Start dev mode'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +106,6 @@ describe('copyBaseApplicationProject', () => {
|
||||
expect(packageJson.devDependencies['twenty-sdk']).toBe(
|
||||
createTwentyAppPackageJson.version,
|
||||
);
|
||||
expect(packageJson.devDependencies['twenty-client-sdk']).toBe(
|
||||
createTwentyAppPackageJson.version,
|
||||
);
|
||||
expect(packageJson.scripts['twenty']).toBe('twenty');
|
||||
});
|
||||
|
||||
@@ -365,21 +362,11 @@ describe('copyBaseApplicationProject', () => {
|
||||
join(srcPath, 'logic-functions', 'hello-world.ts'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'logic-functions', 'create-hello-world-company.ts'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'front-components', 'hello-world.tsx'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'page-layouts', 'example-record-page-layout.ts'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(join(srcPath, 'views', 'example-view.ts')),
|
||||
).toBe(true);
|
||||
@@ -461,21 +448,11 @@ describe('copyBaseApplicationProject', () => {
|
||||
join(srcPath, 'logic-functions', 'hello-world.ts'),
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'logic-functions', 'create-hello-world-company.ts'),
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'front-components', 'hello-world.tsx'),
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'page-layouts', 'example-record-page-layout.ts'),
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
await fs.pathExists(join(srcPath, 'views', 'example-view.ts')),
|
||||
).toBe(false);
|
||||
@@ -503,7 +480,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
});
|
||||
|
||||
describe('selective examples', () => {
|
||||
it('should create front component and page layout when only front component option is enabled', async () => {
|
||||
it('should create only front component when only that option is enabled', async () => {
|
||||
await copyBaseApplicationProject({
|
||||
appName: 'my-test-app',
|
||||
appDisplayName: 'My Test App',
|
||||
@@ -529,11 +506,6 @@ describe('copyBaseApplicationProject', () => {
|
||||
join(srcPath, 'front-components', 'hello-world.tsx'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'page-layouts', 'example-record-page-layout.ts'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')),
|
||||
).toBe(false);
|
||||
@@ -573,11 +545,6 @@ describe('copyBaseApplicationProject', () => {
|
||||
join(srcPath, 'logic-functions', 'hello-world.ts'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(
|
||||
join(srcPath, 'logic-functions', 'create-hello-world-company.ts'),
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')),
|
||||
).toBe(false);
|
||||
|
||||
@@ -47,17 +47,15 @@ describe('scaffoldIntegrationTest', () => {
|
||||
const content = await fs.readFile(testPath, 'utf8');
|
||||
|
||||
expect(content).toContain(
|
||||
"import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'",
|
||||
"import { appBuild, appUninstall } from 'twenty-sdk/cli'",
|
||||
);
|
||||
expect(content).toContain(
|
||||
"import { MetadataApiClient } from 'twenty-client-sdk/metadata'",
|
||||
"import { MetadataApiClient } from 'twenty-sdk/clients'",
|
||||
);
|
||||
expect(content).toContain(
|
||||
"import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'",
|
||||
);
|
||||
expect(content).toContain('appBuild');
|
||||
expect(content).toContain('appDeploy');
|
||||
expect(content).toContain('appInstall');
|
||||
expect(content).toContain('appUninstall');
|
||||
expect(content).toContain('new MetadataApiClient()');
|
||||
expect(content).toContain('findManyApplications');
|
||||
@@ -138,7 +136,7 @@ describe('scaffoldIntegrationTest', () => {
|
||||
expect(content).toContain('yarn install --immutable');
|
||||
expect(content).toContain('yarn test');
|
||||
expect(content).toContain('TWENTY_API_URL');
|
||||
expect(content).toContain('TWENTY_API_KEY');
|
||||
expect(content).toContain('TWENTY_TEST_API_KEY');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ export const copyBaseApplicationProject = async ({
|
||||
|
||||
await createGitignore(appDirectory);
|
||||
|
||||
await createNvmrc(appDirectory);
|
||||
|
||||
await createPublicAssetDirectory(appDirectory);
|
||||
|
||||
const sourceFolderPath = join(appDirectory, SRC_FOLDER);
|
||||
@@ -71,12 +69,6 @@ export const copyBaseApplicationProject = async ({
|
||||
fileFolder: 'logic-functions',
|
||||
fileName: 'hello-world.ts',
|
||||
});
|
||||
|
||||
await createCreateCompanyFunction({
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'logic-functions',
|
||||
fileName: 'create-hello-world-company.ts',
|
||||
});
|
||||
}
|
||||
|
||||
if (exampleOptions.includeExampleFrontComponent) {
|
||||
@@ -85,12 +77,6 @@ export const copyBaseApplicationProject = async ({
|
||||
fileFolder: 'front-components',
|
||||
fileName: 'hello-world.tsx',
|
||||
});
|
||||
|
||||
await createExamplePageLayout({
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'page-layouts',
|
||||
fileName: 'example-record-page-layout.ts',
|
||||
});
|
||||
}
|
||||
|
||||
if (exampleOptions.includeExampleView) {
|
||||
@@ -200,10 +186,6 @@ yarn-error.log*
|
||||
await fs.writeFile(join(appDirectory, '.gitignore'), gitignoreContent);
|
||||
};
|
||||
|
||||
const createNvmrc = async (appDirectory: string) => {
|
||||
await fs.writeFile(join(appDirectory, '.nvmrc'), '24.5.0\n');
|
||||
};
|
||||
|
||||
const createDefaultRoleConfig = async ({
|
||||
displayName,
|
||||
appDirectory,
|
||||
@@ -248,59 +230,19 @@ const createDefaultFrontComponent = async ({
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
|
||||
const content = `import { useEffect, useState } from 'react';
|
||||
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
|
||||
'${universalIdentifier}';
|
||||
const content = `import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const HelloWorld = () => {
|
||||
const client = new CoreApiClient();
|
||||
const [data, setData] = useState<
|
||||
Pick<CoreSchema.Company, 'name' | 'id'> | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const response = await client.query({
|
||||
company: {
|
||||
name: true,
|
||||
id: true,
|
||||
__args: {
|
||||
filter: {
|
||||
position: {
|
||||
eq: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setData(response.company);
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>Hello, World!</h1>
|
||||
<p>This is your first front component.</p>
|
||||
{data ? (
|
||||
<div>
|
||||
<p>Company name: {data.name}</p>
|
||||
<p>Company id: {data.id}</p>
|
||||
</div>
|
||||
) : (
|
||||
<p>Company not found</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
name: 'hello-world-front-component',
|
||||
description: 'A sample front component',
|
||||
component: HelloWorld,
|
||||
@@ -311,56 +253,6 @@ export default defineFrontComponent({
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createExamplePageLayout = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const pageLayoutUniversalIdentifier = v4();
|
||||
const tabUniversalIdentifier = v4();
|
||||
const widgetUniversalIdentifier = v4();
|
||||
|
||||
const content = `import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/front-components/hello-world';
|
||||
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk';
|
||||
|
||||
export default definePageLayout({
|
||||
universalIdentifier: '${pageLayoutUniversalIdentifier}',
|
||||
name: 'Example Record Page',
|
||||
type: 'RECORD_PAGE',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
tabs: [
|
||||
{
|
||||
universalIdentifier: '${tabUniversalIdentifier}',
|
||||
title: 'Hello World',
|
||||
position: 50,
|
||||
icon: 'IconWorld',
|
||||
layoutMode: PageLayoutTabLayoutMode.CANVAS,
|
||||
widgets: [
|
||||
{
|
||||
universalIdentifier: '${widgetUniversalIdentifier}',
|
||||
title: 'Hello World',
|
||||
type: 'FRONT_COMPONENT',
|
||||
configuration: {
|
||||
configurationType: 'FRONT_COMPONENT',
|
||||
frontComponentUniversalIdentifier:
|
||||
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createDefaultFunction = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
@@ -396,62 +288,6 @@ export default defineLogicFunction({
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createCreateCompanyFunction = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
|
||||
const content = `import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (): Promise<{ message: string }> => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
const { createCompany } = await client.mutation({
|
||||
createCompany: {
|
||||
__args: {
|
||||
data: {
|
||||
name: 'Hello World',
|
||||
},
|
||||
},
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!createCompany?.id || !createCompany?.name) {
|
||||
throw new Error('Failed to create company: missing id or name in response');
|
||||
}
|
||||
|
||||
return {
|
||||
message: \`Created company "\${createCompany.name}" with id \${createCompany.id}\`,
|
||||
};
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
name: 'create-hello-world-company',
|
||||
description: 'Creates a company called Hello World',
|
||||
timeoutSeconds: 5,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/create-hello-world-company',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createDefaultPreInstallFunction = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
@@ -779,7 +615,6 @@ const createPackageJson = async ({
|
||||
'react-dom': '^19.0.0',
|
||||
oxlint: '^0.16.0',
|
||||
'twenty-sdk': createTwentyAppPackageJson.version,
|
||||
'twenty-client-sdk': createTwentyAppPackageJson.version,
|
||||
};
|
||||
|
||||
if (includeExampleIntegrationTest) {
|
||||
@@ -798,7 +633,6 @@ const createPackageJson = async ({
|
||||
npm: 'please-use-yarn',
|
||||
yarn: '>=4.0.2',
|
||||
},
|
||||
keywords: ['twenty-app'],
|
||||
packageManager: 'yarn@4.9.2',
|
||||
scripts,
|
||||
devDependencies,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { exec } from 'child_process';
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
export const install = async (root: string) => {
|
||||
console.log(chalk.gray('Installing yarn dependencies...'));
|
||||
try {
|
||||
await execPromise('corepack enable', { cwd: root });
|
||||
} catch (error: any) {
|
||||
@@ -15,6 +14,6 @@ export const install = async (root: string) => {
|
||||
try {
|
||||
await execPromise('yarn install', { cwd: root });
|
||||
} catch (error: any) {
|
||||
console.warn(chalk.yellow('yarn install failed:'), error.stdout);
|
||||
console.error(chalk.red('yarn install failed:'), error.stdout);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { platform } from 'node:os';
|
||||
|
||||
const DEFAULT_PORT = 2020;
|
||||
|
||||
// Minimal health check — the full implementation lives in twenty-sdk
|
||||
const isServerReady = async (port: number): Promise<boolean> => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://localhost:${port}/healthz`, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
|
||||
return body.status === 'ok';
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
|
||||
export type LocalInstanceResult = {
|
||||
running: boolean;
|
||||
serverUrl?: string;
|
||||
};
|
||||
|
||||
export const setupLocalInstance = async (
|
||||
appDirectory: string,
|
||||
): Promise<LocalInstanceResult> => {
|
||||
console.log('');
|
||||
console.log(chalk.blue('Setting up local Twenty instance...'));
|
||||
|
||||
if (await isServerReady(DEFAULT_PORT)) {
|
||||
const serverUrl = `http://localhost:${DEFAULT_PORT}`;
|
||||
|
||||
console.log(chalk.green(`Twenty server detected on ${serverUrl}.`));
|
||||
|
||||
return { running: true, serverUrl };
|
||||
}
|
||||
|
||||
// Delegate to `twenty server start` from the scaffolded app
|
||||
console.log(chalk.gray('Starting local Twenty server...'));
|
||||
|
||||
try {
|
||||
execSync('yarn twenty server start', {
|
||||
cwd: appDirectory,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Failed to start Twenty server. Run `yarn twenty server start` manually.',
|
||||
),
|
||||
);
|
||||
|
||||
return { running: false };
|
||||
}
|
||||
|
||||
console.log(chalk.gray('Waiting for Twenty to be ready...'));
|
||||
|
||||
const startTime = Date.now();
|
||||
const timeoutMs = 180 * 1000;
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
if (await isServerReady(DEFAULT_PORT)) {
|
||||
const serverUrl = `http://localhost:${DEFAULT_PORT}`;
|
||||
|
||||
console.log(chalk.green(`Twenty server is running on ${serverUrl}.`));
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'Workspace ready — login with tim@apple.dev / tim@apple.dev',
|
||||
),
|
||||
);
|
||||
|
||||
const openCommand = platform() === 'darwin' ? 'open' : 'xdg-open';
|
||||
|
||||
try {
|
||||
execSync(`${openCommand} ${serverUrl}`, { stdio: 'ignore' });
|
||||
} catch {
|
||||
// Ignore if browser can't be opened
|
||||
}
|
||||
|
||||
return { running: true, serverUrl };
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Twenty server did not become healthy in time. Check: yarn twenty server logs',
|
||||
),
|
||||
);
|
||||
|
||||
return { running: false };
|
||||
};
|
||||
@@ -149,17 +149,18 @@ const createIntegrationTest = async ({
|
||||
fileName: string;
|
||||
}) => {
|
||||
const content = `import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
|
||||
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
import { 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();
|
||||
|
||||
describe('App installation', () => {
|
||||
let appInstalled = false;
|
||||
|
||||
beforeAll(async () => {
|
||||
const buildResult = await appBuild({
|
||||
appPath: APP_PATH,
|
||||
tarball: true,
|
||||
onProgress: (message: string) => console.log(\`[build] \${message}\`),
|
||||
});
|
||||
|
||||
@@ -169,27 +170,14 @@ describe('App installation', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const deployResult = await appDeploy({
|
||||
tarballPath: buildResult.data.tarballPath!,
|
||||
onProgress: (message: string) => console.log(\`[deploy] \${message}\`),
|
||||
});
|
||||
|
||||
if (!deployResult.success) {
|
||||
throw new Error(
|
||||
\`Deploy failed: \${deployResult.error?.message ?? 'Unknown error'}\`,
|
||||
);
|
||||
}
|
||||
|
||||
const installResult = await appInstall({ appPath: APP_PATH });
|
||||
|
||||
if (!installResult.success) {
|
||||
throw new Error(
|
||||
\`Install failed: \${installResult.error?.message ?? 'Unknown error'}\`,
|
||||
);
|
||||
}
|
||||
appInstalled = true;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!appInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uninstallResult = await appUninstall({ appPath: APP_PATH });
|
||||
|
||||
if (!uninstallResult.success) {
|
||||
@@ -269,7 +257,7 @@ jobs:
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: \${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: \${{ steps.twenty.outputs.access-token }}
|
||||
TWENTY_TEST_API_KEY: \${{ steps.twenty.outputs.access-token }}
|
||||
`;
|
||||
|
||||
const workflowDir = join(appDirectory, '.github', 'workflows');
|
||||
|
||||
@@ -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-apps/fixtures/postcard-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.
|
||||
|
||||
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
|
||||
First, authenticate to your workspace:
|
||||
|
||||
```bash
|
||||
yarn twenty remote add http://localhost:2020 --as local
|
||||
yarn twenty remote add --local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
||||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add http://localhost:2020 --as local # Authenticate with Twenty
|
||||
yarn twenty remote add --local # Authenticate with Twenty
|
||||
yarn twenty remote status # Check auth status
|
||||
yarn twenty remote switch # Switch default remote
|
||||
yarn twenty remote list # List all configured remotes
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,17 @@
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"twenty": "twenty",
|
||||
"remote:add": "twenty remote add --local",
|
||||
"remote:status": "twenty remote status",
|
||||
"remote:switch": "twenty remote switch",
|
||||
"remote:list": "twenty remote list",
|
||||
"remote:remove": "twenty remote remove",
|
||||
"dev": "twenty dev",
|
||||
"add": "twenty add",
|
||||
"logs": "twenty logs",
|
||||
"exec": "twenty exec",
|
||||
"uninstall": "twenty uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,17 @@
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"twenty": "twenty",
|
||||
"remote:add": "twenty remote add --local",
|
||||
"remote:status": "twenty remote status",
|
||||
"remote:switch": "twenty remote switch",
|
||||
"remote:list": "twenty remote list",
|
||||
"remote:remove": "twenty remote remove",
|
||||
"dev": "twenty dev",
|
||||
"add": "twenty add",
|
||||
"logs": "twenty logs",
|
||||
"exec": "twenty exec",
|
||||
"uninstall": "twenty uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"typescript/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,22 @@
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"twenty": "twenty",
|
||||
"remote:add": "twenty remote add --local",
|
||||
"remote:status": "twenty remote status",
|
||||
"remote:switch": "twenty remote switch",
|
||||
"remote:list": "twenty remote list",
|
||||
"remote:remove": "twenty remote remove",
|
||||
"dev": "twenty dev",
|
||||
"add": "twenty add",
|
||||
"logs": "twenty logs",
|
||||
"exec": "twenty exec",
|
||||
"uninstall": "twenty uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest",
|
||||
"twenty-client-sdk": "latest"
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import {
|
||||
defineField,
|
||||
FieldType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
|
||||
// Field on existing company object
|
||||
export default defineField({
|
||||
universalIdentifier: 'f922fdb8-10a9-4f11-a1d0-992a779f6dff',
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
type: FieldType.BOOLEAN,
|
||||
name: 'canReceivePostcards',
|
||||
label: 'Can Receive Postcards',
|
||||
description: 'Whether the company can receive postcards',
|
||||
icon: 'IconMailbox',
|
||||
defaultValue: false,
|
||||
});
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
export default defineField({
|
||||
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: 'b602dbd9-e511-49ce-b6d3-b697218dc69c',
|
||||
universalIdentifier: '8b9c0d1e-2f3a-4b5c-6d7e-8f9a0b1c2d3e',
|
||||
type: FieldType.SELECT,
|
||||
name: 'category',
|
||||
label: 'Category',
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
export default defineField({
|
||||
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: '7b57bd63-5a4c-46ca-9d52-42c8f02d1df6',
|
||||
universalIdentifier: '7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d',
|
||||
type: FieldType.NUMBER,
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
@@ -6,9 +6,6 @@ export const RECIPIENT_UNIVERSAL_IDENTIFIER =
|
||||
export const RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'd2a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
export const RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: RECIPIENT_UNIVERSAL_IDENTIFIER,
|
||||
nameSingular: 'recipient',
|
||||
@@ -26,10 +23,10 @@ export default defineObject({
|
||||
icon: 'IconMail',
|
||||
},
|
||||
{
|
||||
universalIdentifier: RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: 'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.ADDRESS,
|
||||
label: 'Mailing Address',
|
||||
name: 'mailingAddress',
|
||||
label: 'Address',
|
||||
name: 'address',
|
||||
icon: 'IconHome',
|
||||
},
|
||||
],
|
||||
|
||||
+3
-7
@@ -1,16 +1,13 @@
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
import {
|
||||
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
|
||||
SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
} from '../objects/post-card-recipient.object';
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
|
||||
export const ALL_POST_CARD_RECIPIENTS_VIEW_ID =
|
||||
'b1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
export const POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'fd959c6f-3465-4a3a-b7ad-3f4004fffc9a';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: ALL_POST_CARD_RECIPIENTS_VIEW_ID,
|
||||
name: 'All Post Card Recipients',
|
||||
@@ -20,8 +17,7 @@ export default defineView({
|
||||
position: 2,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier:
|
||||
POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
fieldMetadataUniversalIdentifier: SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
import {
|
||||
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
RECIPIENT_UNIVERSAL_IDENTIFIER,
|
||||
} from '../objects/recipient.object';
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { ViewType } from 'twenty-shared/types';
|
||||
|
||||
export const ALL_RECIPIENTS_VIEW_ID = 'b1a2b3c4-0002-4a7b-8c9d-0e1f2a3b4c5d';
|
||||
|
||||
@@ -24,13 +23,5 @@ export default defineView({
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
fieldMetadataUniversalIdentifier:
|
||||
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run integration tests
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
@@ -1,10 +1,9 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-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.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
|
||||
First, authenticate to your workspace:
|
||||
|
||||
```bash
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local
|
||||
yarn twenty remote add --local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
||||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
|
||||
yarn twenty remote add --local # Authenticate with Twenty
|
||||
yarn twenty remote status # Check auth status
|
||||
yarn twenty remote switch # Switch default remote
|
||||
yarn twenty remote list # List all configured remotes
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
"@types/react": "^18.2.0",
|
||||
"oxlint": "^0.16.0",
|
||||
"react": "^18.2.0",
|
||||
"twenty-client-sdk": "0.8.0-canary.5",
|
||||
"twenty-sdk": "0.8.0-canary.5",
|
||||
"twenty-sdk": "0.6.4",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"vitest": "^3.1.1"
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
|
||||
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
import { 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();
|
||||
|
||||
describe('App installation', () => {
|
||||
let appInstalled = false;
|
||||
|
||||
beforeAll(async () => {
|
||||
const buildResult = await appBuild({
|
||||
appPath: APP_PATH,
|
||||
tarball: true,
|
||||
onProgress: (message: string) => console.log(`[build] ${message}`),
|
||||
});
|
||||
|
||||
@@ -19,27 +20,14 @@ describe('App installation', () => {
|
||||
);
|
||||
}
|
||||
|
||||
const deployResult = await appDeploy({
|
||||
tarballPath: buildResult.data.tarballPath!,
|
||||
onProgress: (message: string) => console.log(`[deploy] ${message}`),
|
||||
});
|
||||
|
||||
if (!deployResult.success) {
|
||||
throw new Error(
|
||||
`Deploy failed: ${deployResult.error?.message ?? 'Unknown error'}`,
|
||||
);
|
||||
}
|
||||
|
||||
const installResult = await appInstall({ appPath: APP_PATH });
|
||||
|
||||
if (!installResult.success) {
|
||||
throw new Error(
|
||||
`Install failed: ${installResult.error?.message ?? 'Unknown error'}`,
|
||||
);
|
||||
}
|
||||
appInstalled = true;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!appInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uninstallResult = await appUninstall({ appPath: APP_PATH });
|
||||
|
||||
if (!uninstallResult.success) {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { defineAgent } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_AGENT_UNIVERSAL_IDENTIFIER =
|
||||
'110bebc2-f116-46b6-a35d-61e91c3c0a43';
|
||||
|
||||
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.',
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export const APPLICATION_UNIVERSAL_IDENTIFIER =
|
||||
'bb1decf6-dee5-43ef-b881-9799f97b02a8';
|
||||
'6563e091-9f5b-4026-a3ea-7e3b3d09e218';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'
|
||||
|
||||
export default defineField({
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: 'be08a7c6-2586-4d91-9fa7-0a44e6eae30c',
|
||||
universalIdentifier: '770d32c2-cf12-4ab2-b66d-73f92dc239b5',
|
||||
type: FieldType.NUMBER,
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
|
||||
@@ -1,56 +1,16 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
|
||||
'7a758f23-5e7d-497d-98c9-7ca8d6c085b0';
|
||||
|
||||
export const HelloWorld = () => {
|
||||
const client = new CoreApiClient();
|
||||
const [data, setData] = useState<
|
||||
Pick<CoreSchema.Company, 'name' | 'id'> | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const response = await client.query({
|
||||
company: {
|
||||
name: true,
|
||||
id: true,
|
||||
__args: {
|
||||
filter: {
|
||||
position: {
|
||||
eq: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setData(response.company);
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>Hello, World!</h1>
|
||||
<p>This is your first front component.</p>
|
||||
{data ? (
|
||||
<div>
|
||||
<p>Company name: {data.name}</p>
|
||||
<p>Company id: {data.id}</p>
|
||||
</div>
|
||||
) : (
|
||||
<p>Company not found</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: 'd371f098-5b2c-42f0-898d-94459f1ee337',
|
||||
name: 'hello-world-front-component',
|
||||
description: 'A sample front component',
|
||||
component: HelloWorld,
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (): Promise<{ message: string }> => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
const { createCompany } = await client.mutation({
|
||||
createCompany: {
|
||||
__args: {
|
||||
data: {
|
||||
name: 'Hello World',
|
||||
},
|
||||
},
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!createCompany?.id || !createCompany?.name) {
|
||||
throw new Error('Failed to create company: missing id or name in response');
|
||||
}
|
||||
|
||||
return {
|
||||
message: `Created company "${createCompany.name}" with id ${createCompany.id}`,
|
||||
};
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '94abaa53-d265-4fa4-ae52-1d7ea711ecf2',
|
||||
name: 'create-hello-world-company',
|
||||
description: 'Creates a company called Hello World',
|
||||
timeoutSeconds: 5,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/create-hello-world-company',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
@@ -5,7 +5,7 @@ const handler = async (): Promise<{ message: string }> => {
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'b05e4b30-72d4-4d7f-8091-32e037b601da',
|
||||
universalIdentifier: '2baa26eb-9aaf-4856-a4f4-30d6fd6480ee',
|
||||
name: 'hello-world-logic-function',
|
||||
description: 'A simple logic function',
|
||||
timeoutSeconds: 5,
|
||||
|
||||
@@ -5,7 +5,7 @@ const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '8c726dcc-1709-4eac-aa8b-f99960a9ec1b',
|
||||
universalIdentifier: '7a3f4684-51db-494d-833b-a747a3b90507',
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
|
||||
@@ -5,7 +5,7 @@ const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: 'f8ad4b09-6a12-4b12-a52a-3472d3a78dc7',
|
||||
universalIdentifier: '1272ffdb-8e2f-492c-ab37-66c2b97e9c23',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c',
|
||||
universalIdentifier: '10f90627-e9c2-44b7-9742-bed77e3d1b17',
|
||||
name: 'example-navigation-menu-item',
|
||||
icon: 'IconList',
|
||||
color: 'blue',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER =
|
||||
'47fd9bd9-392b-4d9f-9091-9a91b1edf519';
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928';
|
||||
|
||||
export const NAME_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'2d9ff841-cf8e-44ec-ad8e-468455f7eebd';
|
||||
'd2d7f6cd-33f6-456f-bf00-17adeca926ba';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
@@ -14,8 +14,7 @@ export default defineObject({
|
||||
labelPlural: 'Example items',
|
||||
description: 'A sample custom object',
|
||||
icon: 'IconBox',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/front-components/hello-world';
|
||||
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk';
|
||||
|
||||
export default definePageLayout({
|
||||
universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134',
|
||||
name: 'Example Record Page',
|
||||
type: 'RECORD_PAGE',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
tabs: [
|
||||
{
|
||||
universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5',
|
||||
title: 'Hello World',
|
||||
position: 50,
|
||||
icon: 'IconWorld',
|
||||
layoutMode: PageLayoutTabLayoutMode.CANVAS,
|
||||
widgets: [
|
||||
{
|
||||
universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d',
|
||||
title: 'Hello World',
|
||||
type: 'FRONT_COMPONENT',
|
||||
configuration: {
|
||||
configurationType: 'FRONT_COMPONENT',
|
||||
frontComponentUniversalIdentifier:
|
||||
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'c38f4d11-760c-4d5c-89ed-e569c28b7b70';
|
||||
'9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineSkill } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER =
|
||||
'90cf9144-4811-4653-93a2-9a6780fe6aac';
|
||||
'd0940029-9d3c-40be-903a-52d65393028f';
|
||||
|
||||
export default defineSkill({
|
||||
universalIdentifier: EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineView, ViewKey } from 'twenty-sdk';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
|
||||
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = '965e3776-b966-4be8-83f7-6cd3bce5e1bd';
|
||||
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = 'e004df40-29f3-47ba-b39d-d3a5c444367a';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
@@ -12,7 +12,7 @@ export default defineView({
|
||||
position: 0,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0',
|
||||
universalIdentifier: '496c40c2-5766-419c-93bf-20fdad3f34bb',
|
||||
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
|
||||
@@ -5,13 +5,13 @@ __metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"@alcalzone/ansi-tokenize@npm:^0.2.4":
|
||||
version: 0.2.5
|
||||
resolution: "@alcalzone/ansi-tokenize@npm:0.2.5"
|
||||
"@alcalzone/ansi-tokenize@npm:^0.1.3":
|
||||
version: 0.1.3
|
||||
resolution: "@alcalzone/ansi-tokenize@npm:0.1.3"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^6.2.1"
|
||||
is-fullwidth-code-point: "npm:^5.0.0"
|
||||
checksum: 10c0/dd8622288426b5b7dbf8b68d51d4b930cea591d0e3b9dbc3f523131464d78ac922c165fcb7ba5307f133d35dbcaa0e6648a1c3fb6a0dc2725546d6f867b70af4
|
||||
is-fullwidth-code-point: "npm:^4.0.0"
|
||||
checksum: 10c0/b88c5708271bb64ce132fc80dac8d5b87fc1699bf3abfdf10ecae40dbb56ab82460818f5746ecdd9870a00be9854e039d5cb3a121b808d0ff6f5bc7d0146cb38
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -352,9 +352,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/aix-ppc64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.27.4"
|
||||
"@esbuild/aix-ppc64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.27.3"
|
||||
conditions: os=aix & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -366,9 +366,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/android-arm64@npm:0.27.4"
|
||||
"@esbuild/android-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/android-arm64@npm:0.27.3"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -380,9 +380,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/android-arm@npm:0.27.4"
|
||||
"@esbuild/android-arm@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/android-arm@npm:0.27.3"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -394,9 +394,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/android-x64@npm:0.27.4"
|
||||
"@esbuild/android-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/android-x64@npm:0.27.3"
|
||||
conditions: os=android & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -408,9 +408,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.27.4"
|
||||
"@esbuild/darwin-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.27.3"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -422,9 +422,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/darwin-x64@npm:0.27.4"
|
||||
"@esbuild/darwin-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/darwin-x64@npm:0.27.3"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -436,9 +436,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.27.4"
|
||||
"@esbuild/freebsd-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.27.3"
|
||||
conditions: os=freebsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -450,9 +450,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.27.4"
|
||||
"@esbuild/freebsd-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.27.3"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -464,9 +464,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-arm64@npm:0.27.4"
|
||||
"@esbuild/linux-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-arm64@npm:0.27.3"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -478,9 +478,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-arm@npm:0.27.4"
|
||||
"@esbuild/linux-arm@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-arm@npm:0.27.3"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -492,9 +492,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ia32@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-ia32@npm:0.27.4"
|
||||
"@esbuild/linux-ia32@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-ia32@npm:0.27.3"
|
||||
conditions: os=linux & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -506,9 +506,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-loong64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-loong64@npm:0.27.4"
|
||||
"@esbuild/linux-loong64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-loong64@npm:0.27.3"
|
||||
conditions: os=linux & cpu=loong64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -520,9 +520,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-mips64el@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.27.4"
|
||||
"@esbuild/linux-mips64el@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.27.3"
|
||||
conditions: os=linux & cpu=mips64el
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -534,9 +534,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ppc64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.27.4"
|
||||
"@esbuild/linux-ppc64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.27.3"
|
||||
conditions: os=linux & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -548,9 +548,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-riscv64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.27.4"
|
||||
"@esbuild/linux-riscv64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.27.3"
|
||||
conditions: os=linux & cpu=riscv64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -562,9 +562,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-s390x@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-s390x@npm:0.27.4"
|
||||
"@esbuild/linux-s390x@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-s390x@npm:0.27.3"
|
||||
conditions: os=linux & cpu=s390x
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -576,9 +576,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/linux-x64@npm:0.27.4"
|
||||
"@esbuild/linux-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/linux-x64@npm:0.27.3"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -590,9 +590,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/netbsd-arm64@npm:0.27.4"
|
||||
"@esbuild/netbsd-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/netbsd-arm64@npm:0.27.3"
|
||||
conditions: os=netbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -604,9 +604,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.27.4"
|
||||
"@esbuild/netbsd-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.27.3"
|
||||
conditions: os=netbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -618,9 +618,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.27.4"
|
||||
"@esbuild/openbsd-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.27.3"
|
||||
conditions: os=openbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -632,9 +632,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.27.4"
|
||||
"@esbuild/openbsd-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.27.3"
|
||||
conditions: os=openbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -646,9 +646,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openharmony-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/openharmony-arm64@npm:0.27.4"
|
||||
"@esbuild/openharmony-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/openharmony-arm64@npm:0.27.3"
|
||||
conditions: os=openharmony & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -660,9 +660,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/sunos-x64@npm:0.27.4"
|
||||
"@esbuild/sunos-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/sunos-x64@npm:0.27.3"
|
||||
conditions: os=sunos & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -674,9 +674,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-arm64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/win32-arm64@npm:0.27.4"
|
||||
"@esbuild/win32-arm64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/win32-arm64@npm:0.27.3"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -688,9 +688,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-ia32@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/win32-ia32@npm:0.27.4"
|
||||
"@esbuild/win32-ia32@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/win32-ia32@npm:0.27.3"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -702,9 +702,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-x64@npm:0.27.4":
|
||||
version: 0.27.4
|
||||
resolution: "@esbuild/win32-x64@npm:0.27.4"
|
||||
"@esbuild/win32-x64@npm:0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "@esbuild/win32-x64@npm:0.27.3"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -1541,11 +1541,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:*":
|
||||
version: 25.5.0
|
||||
resolution: "@types/node@npm:25.5.0"
|
||||
version: 25.3.5
|
||||
resolution: "@types/node@npm:25.3.5"
|
||||
dependencies:
|
||||
undici-types: "npm:~7.18.0"
|
||||
checksum: 10c0/70c508165b6758c4f88d4f91abca526c3985eee1985503d4c2bd994dbaf588e52ac57e571160f18f117d76e963570ac82bd20e743c18987e82564312b3b62119
|
||||
checksum: 10c0/4cf0834a6f6933bf0aca6afead117ae3db3b8f02a5f7187a24f871db0fb9344e5e46573ba387bc53b7505e1e219c4c535cbe67221ced95bb5ad98573223b19d0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2655,7 +2655,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-escapes@npm:^7.3.0":
|
||||
"ansi-escapes@npm:^7.0.0":
|
||||
version: 7.3.0
|
||||
resolution: "ansi-escapes@npm:7.3.0"
|
||||
dependencies:
|
||||
@@ -2717,7 +2717,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3":
|
||||
"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.2.1":
|
||||
version: 6.2.3
|
||||
resolution: "ansi-styles@npm:6.2.3"
|
||||
checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868
|
||||
@@ -2948,7 +2948,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^5.3.0, chalk@npm:^5.6.0":
|
||||
"chalk@npm:^5.3.0":
|
||||
version: 5.6.2
|
||||
resolution: "chalk@npm:5.6.2"
|
||||
checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976
|
||||
@@ -3020,13 +3020,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-truncate@npm:^5.1.1":
|
||||
version: 5.2.0
|
||||
resolution: "cli-truncate@npm:5.2.0"
|
||||
"cli-truncate@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "cli-truncate@npm:4.0.0"
|
||||
dependencies:
|
||||
slice-ansi: "npm:^8.0.0"
|
||||
string-width: "npm:^8.2.0"
|
||||
checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471
|
||||
slice-ansi: "npm:^5.0.0"
|
||||
string-width: "npm:^7.0.0"
|
||||
checksum: 10c0/d7f0b73e3d9b88cb496e6c086df7410b541b56a43d18ade6a573c9c18bd001b1c3fba1ad578f741a4218fdc794d042385f8ac02c25e1c295a2d8b9f3cb86eb4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3306,7 +3306,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-toolkit@npm:^1.39.10":
|
||||
"es-toolkit@npm:^1.22.0":
|
||||
version: 1.45.1
|
||||
resolution: "es-toolkit@npm:1.45.1"
|
||||
dependenciesMeta:
|
||||
@@ -3408,35 +3408,35 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.27.0":
|
||||
version: 0.27.4
|
||||
resolution: "esbuild@npm:0.27.4"
|
||||
version: 0.27.3
|
||||
resolution: "esbuild@npm:0.27.3"
|
||||
dependencies:
|
||||
"@esbuild/aix-ppc64": "npm:0.27.4"
|
||||
"@esbuild/android-arm": "npm:0.27.4"
|
||||
"@esbuild/android-arm64": "npm:0.27.4"
|
||||
"@esbuild/android-x64": "npm:0.27.4"
|
||||
"@esbuild/darwin-arm64": "npm:0.27.4"
|
||||
"@esbuild/darwin-x64": "npm:0.27.4"
|
||||
"@esbuild/freebsd-arm64": "npm:0.27.4"
|
||||
"@esbuild/freebsd-x64": "npm:0.27.4"
|
||||
"@esbuild/linux-arm": "npm:0.27.4"
|
||||
"@esbuild/linux-arm64": "npm:0.27.4"
|
||||
"@esbuild/linux-ia32": "npm:0.27.4"
|
||||
"@esbuild/linux-loong64": "npm:0.27.4"
|
||||
"@esbuild/linux-mips64el": "npm:0.27.4"
|
||||
"@esbuild/linux-ppc64": "npm:0.27.4"
|
||||
"@esbuild/linux-riscv64": "npm:0.27.4"
|
||||
"@esbuild/linux-s390x": "npm:0.27.4"
|
||||
"@esbuild/linux-x64": "npm:0.27.4"
|
||||
"@esbuild/netbsd-arm64": "npm:0.27.4"
|
||||
"@esbuild/netbsd-x64": "npm:0.27.4"
|
||||
"@esbuild/openbsd-arm64": "npm:0.27.4"
|
||||
"@esbuild/openbsd-x64": "npm:0.27.4"
|
||||
"@esbuild/openharmony-arm64": "npm:0.27.4"
|
||||
"@esbuild/sunos-x64": "npm:0.27.4"
|
||||
"@esbuild/win32-arm64": "npm:0.27.4"
|
||||
"@esbuild/win32-ia32": "npm:0.27.4"
|
||||
"@esbuild/win32-x64": "npm:0.27.4"
|
||||
"@esbuild/aix-ppc64": "npm:0.27.3"
|
||||
"@esbuild/android-arm": "npm:0.27.3"
|
||||
"@esbuild/android-arm64": "npm:0.27.3"
|
||||
"@esbuild/android-x64": "npm:0.27.3"
|
||||
"@esbuild/darwin-arm64": "npm:0.27.3"
|
||||
"@esbuild/darwin-x64": "npm:0.27.3"
|
||||
"@esbuild/freebsd-arm64": "npm:0.27.3"
|
||||
"@esbuild/freebsd-x64": "npm:0.27.3"
|
||||
"@esbuild/linux-arm": "npm:0.27.3"
|
||||
"@esbuild/linux-arm64": "npm:0.27.3"
|
||||
"@esbuild/linux-ia32": "npm:0.27.3"
|
||||
"@esbuild/linux-loong64": "npm:0.27.3"
|
||||
"@esbuild/linux-mips64el": "npm:0.27.3"
|
||||
"@esbuild/linux-ppc64": "npm:0.27.3"
|
||||
"@esbuild/linux-riscv64": "npm:0.27.3"
|
||||
"@esbuild/linux-s390x": "npm:0.27.3"
|
||||
"@esbuild/linux-x64": "npm:0.27.3"
|
||||
"@esbuild/netbsd-arm64": "npm:0.27.3"
|
||||
"@esbuild/netbsd-x64": "npm:0.27.3"
|
||||
"@esbuild/openbsd-arm64": "npm:0.27.3"
|
||||
"@esbuild/openbsd-x64": "npm:0.27.3"
|
||||
"@esbuild/openharmony-arm64": "npm:0.27.3"
|
||||
"@esbuild/sunos-x64": "npm:0.27.3"
|
||||
"@esbuild/win32-arm64": "npm:0.27.3"
|
||||
"@esbuild/win32-ia32": "npm:0.27.3"
|
||||
"@esbuild/win32-x64": "npm:0.27.3"
|
||||
dependenciesMeta:
|
||||
"@esbuild/aix-ppc64":
|
||||
optional: true
|
||||
@@ -3492,7 +3492,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
esbuild: bin/esbuild
|
||||
checksum: 10c0/2a1c2bcccda279f2afd72a7f8259860cb4483b32453d17878e1ecb4ac416b9e7c1001e7aa0a25ba4c29c1e250a3ceaae5d8bb72a119815bc8db4e9b5f5321490
|
||||
checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3727,7 +3727,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0":
|
||||
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1":
|
||||
version: 1.5.0
|
||||
resolution: "get-east-asian-width@npm:1.5.0"
|
||||
checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167
|
||||
@@ -3906,8 +3906,7 @@ __metadata:
|
||||
"@types/react": "npm:^18.2.0"
|
||||
oxlint: "npm:^0.16.0"
|
||||
react: "npm:^18.2.0"
|
||||
twenty-client-sdk: "npm:0.8.0-canary.5"
|
||||
twenty-sdk: "npm:0.8.0-canary.5"
|
||||
twenty-sdk: "portal:../../twenty-sdk"
|
||||
typescript: "npm:^5.9.3"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
vitest: "npm:^3.1.1"
|
||||
@@ -4030,45 +4029,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ink@npm:^6.8.0":
|
||||
version: 6.8.0
|
||||
resolution: "ink@npm:6.8.0"
|
||||
"ink@npm:^5.1.1":
|
||||
version: 5.2.1
|
||||
resolution: "ink@npm:5.2.1"
|
||||
dependencies:
|
||||
"@alcalzone/ansi-tokenize": "npm:^0.2.4"
|
||||
ansi-escapes: "npm:^7.3.0"
|
||||
"@alcalzone/ansi-tokenize": "npm:^0.1.3"
|
||||
ansi-escapes: "npm:^7.0.0"
|
||||
ansi-styles: "npm:^6.2.1"
|
||||
auto-bind: "npm:^5.0.1"
|
||||
chalk: "npm:^5.6.0"
|
||||
chalk: "npm:^5.3.0"
|
||||
cli-boxes: "npm:^3.0.0"
|
||||
cli-cursor: "npm:^4.0.0"
|
||||
cli-truncate: "npm:^5.1.1"
|
||||
cli-truncate: "npm:^4.0.0"
|
||||
code-excerpt: "npm:^4.0.0"
|
||||
es-toolkit: "npm:^1.39.10"
|
||||
es-toolkit: "npm:^1.22.0"
|
||||
indent-string: "npm:^5.0.0"
|
||||
is-in-ci: "npm:^2.0.0"
|
||||
is-in-ci: "npm:^1.0.0"
|
||||
patch-console: "npm:^2.0.0"
|
||||
react-reconciler: "npm:^0.33.0"
|
||||
scheduler: "npm:^0.27.0"
|
||||
react-reconciler: "npm:^0.29.0"
|
||||
scheduler: "npm:^0.23.0"
|
||||
signal-exit: "npm:^3.0.7"
|
||||
slice-ansi: "npm:^8.0.0"
|
||||
slice-ansi: "npm:^7.1.0"
|
||||
stack-utils: "npm:^2.0.6"
|
||||
string-width: "npm:^8.1.1"
|
||||
terminal-size: "npm:^4.0.1"
|
||||
type-fest: "npm:^5.4.1"
|
||||
widest-line: "npm:^6.0.0"
|
||||
string-width: "npm:^7.2.0"
|
||||
type-fest: "npm:^4.27.0"
|
||||
widest-line: "npm:^5.0.0"
|
||||
wrap-ansi: "npm:^9.0.0"
|
||||
ws: "npm:^8.18.0"
|
||||
yoga-layout: "npm:~3.2.1"
|
||||
peerDependencies:
|
||||
"@types/react": ">=19.0.0"
|
||||
react: ">=19.0.0"
|
||||
react-devtools-core: ">=6.1.2"
|
||||
"@types/react": ">=18.0.0"
|
||||
react: ">=18.0.0"
|
||||
react-devtools-core: ^4.19.1
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
react-devtools-core:
|
||||
optional: true
|
||||
checksum: 10c0/50500e547fdf6a1f1d836d6befbd4770e3ab649ef0be1884500a6da411fb68a90e22dd7dcc9c404911d30e9f87506b3b9d8e997c6c6ceac85ee054b4dadefaff
|
||||
checksum: 10c0/0e2607712783353fbe99438ca4220db3d5874c7ae1a07e2b232f7539489b13a9db929b3046eedeccf318a3ffa222a572287dbe2307c848b8e7f6ec4bba39e550
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4141,7 +4139,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0":
|
||||
"is-fullwidth-code-point@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "is-fullwidth-code-point@npm:4.0.0"
|
||||
checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-fullwidth-code-point@npm:^5.0.0":
|
||||
version: 5.1.0
|
||||
resolution: "is-fullwidth-code-point@npm:5.1.0"
|
||||
dependencies:
|
||||
@@ -4159,12 +4164,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-in-ci@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "is-in-ci@npm:2.0.0"
|
||||
"is-in-ci@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "is-in-ci@npm:1.0.0"
|
||||
bin:
|
||||
is-in-ci: cli.js
|
||||
checksum: 10c0/1e1d1056939a681e8206035de5ad84e0404556eaa7622bb55f0f1868b9788bff3df427bc0b1ed5a172623154a90fcb1e759a230817cd73d09435543ae3c71feb
|
||||
checksum: 10c0/98f9cec4c35aece4cf731abf35ebf28359a9b0324fac810da05b842515d9ccb33b8999c1d9a679f0362e1a4df3292065c38d7f86327b1387fa667bc0150f4898
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4947,9 +4952,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"preact@npm:^10.28.3":
|
||||
version: 10.29.0
|
||||
resolution: "preact@npm:10.29.0"
|
||||
checksum: 10c0/d111381e5b48335e3a797a03adb83521cf5e9bdf880570fb2eff4fe9da9c82e6dedcbdf54538b1ed8f60bf813a0df0f4891b03dc32140ad93f8f720a8812dd5c
|
||||
version: 10.28.4
|
||||
resolution: "preact@npm:10.28.4"
|
||||
checksum: 10c0/712384e92d6c676c8f4c230eab81329a66acfecf700390aebfe5fc46168fa3686345d47f2292068dcc0a6d86425a3d9a3d743730e356e867a8402442afb989db
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5008,14 +5013,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-dom@npm:^19.0.0":
|
||||
version: 19.2.4
|
||||
resolution: "react-dom@npm:19.2.4"
|
||||
"react-dom@npm:^18.2.0":
|
||||
version: 18.3.1
|
||||
resolution: "react-dom@npm:18.3.1"
|
||||
dependencies:
|
||||
scheduler: "npm:^0.27.0"
|
||||
loose-envify: "npm:^1.1.0"
|
||||
scheduler: "npm:^0.23.2"
|
||||
peerDependencies:
|
||||
react: ^19.2.4
|
||||
checksum: 10c0/f0c63f1794dedb154136d4d0f59af00b41907f4859571c155940296808f4b94bf9c0c20633db75b5b2112ec13d8d7dd4f9bf57362ed48782f317b11d05a44f35
|
||||
react: ^18.3.1
|
||||
checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5026,14 +5032,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-reconciler@npm:^0.33.0":
|
||||
version: 0.33.0
|
||||
resolution: "react-reconciler@npm:0.33.0"
|
||||
"react-reconciler@npm:^0.29.0":
|
||||
version: 0.29.2
|
||||
resolution: "react-reconciler@npm:0.29.2"
|
||||
dependencies:
|
||||
scheduler: "npm:^0.27.0"
|
||||
loose-envify: "npm:^1.1.0"
|
||||
scheduler: "npm:^0.23.2"
|
||||
peerDependencies:
|
||||
react: ^19.2.0
|
||||
checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a
|
||||
react: ^18.3.1
|
||||
checksum: 10c0/94f48ddc348a974256cf13c859f5a94efdb0cd72e04c51b1a4d5c72a8b960ccd35df2196057ee6a4cbcb26145e12b01e3f9ba3b183fddb901414db36a07cbf43
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5046,13 +5053,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:^19.0.0":
|
||||
version: 19.2.4
|
||||
resolution: "react@npm:19.2.4"
|
||||
checksum: 10c0/cd2c9ff67a720799cc3b38a516009986f7fc4cb8d3e15716c6211cf098d1357ee3e348ab05ad0600042bbb0fd888530ba92e329198c92eafa0994f5213396596
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"readdirp@npm:^4.0.1":
|
||||
version: 4.1.2
|
||||
resolution: "readdirp@npm:4.1.2"
|
||||
@@ -5297,10 +5297,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"scheduler@npm:^0.27.0":
|
||||
version: 0.27.0
|
||||
resolution: "scheduler@npm:0.27.0"
|
||||
checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452
|
||||
"scheduler@npm:^0.23.0, scheduler@npm:^0.23.2":
|
||||
version: 0.23.2
|
||||
resolution: "scheduler@npm:0.23.2"
|
||||
dependencies:
|
||||
loose-envify: "npm:^1.1.0"
|
||||
checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5403,13 +5405,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slice-ansi@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "slice-ansi@npm:8.0.0"
|
||||
"slice-ansi@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "slice-ansi@npm:5.0.0"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^6.2.3"
|
||||
is-fullwidth-code-point: "npm:^5.1.0"
|
||||
checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a
|
||||
ansi-styles: "npm:^6.0.0"
|
||||
is-fullwidth-code-point: "npm:^4.0.0"
|
||||
checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"slice-ansi@npm:^7.1.0":
|
||||
version: 7.1.2
|
||||
resolution: "slice-ansi@npm:7.1.2"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^6.2.1"
|
||||
is-fullwidth-code-point: "npm:^5.0.0"
|
||||
checksum: 10c0/36742f2eb0c03e2e81a38ed14d13a64f7b732fe38c3faf96cce0599788a345011e840db35f1430ca606ea3f8db2abeb92a8d25c2753a819e3babaa10c2e289a2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5519,7 +5531,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^7.0.0":
|
||||
"string-width@npm:^7.0.0, string-width@npm:^7.2.0":
|
||||
version: 7.2.0
|
||||
resolution: "string-width@npm:7.2.0"
|
||||
dependencies:
|
||||
@@ -5530,16 +5542,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^8.1.0, string-width@npm:^8.1.1, string-width@npm:^8.2.0":
|
||||
version: 8.2.0
|
||||
resolution: "string-width@npm:8.2.0"
|
||||
dependencies:
|
||||
get-east-asian-width: "npm:^1.5.0"
|
||||
strip-ansi: "npm:^7.1.2"
|
||||
checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "strip-ansi@npm:3.0.1"
|
||||
@@ -5567,7 +5569,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2":
|
||||
"strip-ansi@npm:^7.1.0":
|
||||
version: 7.2.0
|
||||
resolution: "strip-ansi@npm:7.2.0"
|
||||
dependencies:
|
||||
@@ -5637,13 +5639,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tagged-tag@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "tagged-tag@npm:1.0.0"
|
||||
checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:^7.5.4":
|
||||
version: 7.5.11
|
||||
resolution: "tar@npm:7.5.11"
|
||||
@@ -5657,13 +5652,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terminal-size@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "terminal-size@npm:4.0.1"
|
||||
checksum: 10c0/89afd9d816dd9dbfe4499da9aeea70491bbde4ff4592226a9c8ac71074a7580afead6a78e95ecc35f6d42e09087b55ffcb1019302cd55e0cc957b6ce5c4847e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinybench@npm:^2.9.0":
|
||||
version: 2.9.0
|
||||
resolution: "tinybench@npm:2.9.0"
|
||||
@@ -5762,21 +5750,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-client-sdk@npm:0.8.0-canary.5":
|
||||
version: 0.8.0-canary.5
|
||||
resolution: "twenty-client-sdk@npm:0.8.0-canary.5"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
esbuild: "npm:^0.25.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
checksum: 10c0/754b92b732c8e03c9779f6557324710afe4c07f7e6efbe766bd8ff3b6dd8a00c0d9f3fecce2098d68ef9556cafa722b2189e490473e420fdb9cf30c56beb3619
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-sdk@npm:0.8.0-canary.5":
|
||||
version: 0.8.0-canary.5
|
||||
resolution: "twenty-sdk@npm:0.8.0-canary.5"
|
||||
"twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A."
|
||||
dependencies:
|
||||
"@chakra-ui/react": "npm:^3.33.0"
|
||||
"@emotion/react": "npm:^11.14.0"
|
||||
@@ -5794,14 +5770,13 @@ __metadata:
|
||||
esbuild: "npm:^0.25.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
graphql-sse: "npm:^2.5.4"
|
||||
ink: "npm:^6.8.0"
|
||||
ink: "npm:^5.1.1"
|
||||
inquirer: "npm:^10.0.0"
|
||||
jsonc-parser: "npm:^3.2.0"
|
||||
preact: "npm:^10.28.3"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
react: "npm:^18.2.0"
|
||||
react-dom: "npm:^18.2.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
twenty-client-sdk: "npm:0.8.0-canary.5"
|
||||
typescript: "npm:^5.9.2"
|
||||
uuid: "npm:^13.0.0"
|
||||
vite: "npm:^7.0.0"
|
||||
@@ -5809,9 +5784,8 @@ __metadata:
|
||||
zod: "npm:^4.1.11"
|
||||
bin:
|
||||
twenty: dist/cli.cjs
|
||||
checksum: 10c0/47d0970c88f59c092e8eca34dca38bf88d9d52678997349068ca8a8e5cebed2ea71dbd4e8b7299f40b99b5419d4cecd53b31f821b65959267e8d97eb91d2ddde
|
||||
languageName: node
|
||||
linkType: hard
|
||||
linkType: soft
|
||||
|
||||
"type-fest@npm:^0.21.3":
|
||||
version: 0.21.3
|
||||
@@ -5820,12 +5794,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^5.4.1":
|
||||
version: 5.5.0
|
||||
resolution: "type-fest@npm:5.5.0"
|
||||
dependencies:
|
||||
tagged-tag: "npm:^1.0.0"
|
||||
checksum: 10c0/60bf79a8df45abf99490e3204eceb5cf7f915413f8a69fb578c75cab37ddcb7d29ee21f185f0e1617323ac0b2a441e001b8dc691e220d0b087e9c29ea205538c
|
||||
"type-fest@npm:^4.27.0":
|
||||
version: 4.41.0
|
||||
resolution: "type-fest@npm:4.41.0"
|
||||
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6139,12 +6111,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"widest-line@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "widest-line@npm:6.0.0"
|
||||
"widest-line@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "widest-line@npm:5.0.0"
|
||||
dependencies:
|
||||
string-width: "npm:^8.1.0"
|
||||
checksum: 10c0/735f1fdcd97fe765a07bb8b5e73c020bed8e53ab34e83ce0ef01693ba3c914d9e7977fe5f5facf0d0b670297a82dd5e376d3efa0896860dfcdaf7cd6924c0fb7
|
||||
string-width: "npm:^7.0.0"
|
||||
checksum: 10c0/6bd6cca8cda502ef50e05353fd25de0df8c704ffc43ada7e0a9cf9a5d4f4e12520485d80e0b77cec8a21f6c3909042fcf732aa9281e5dbb98cc9384a138b2578
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Base documentation
|
||||
|
||||
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
|
||||
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
|
||||
First, authenticate to your workspace:
|
||||
|
||||
```bash
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local
|
||||
yarn twenty remote add --local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
||||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
|
||||
yarn twenty remote add --local # Authenticate with Twenty
|
||||
yarn twenty remote status # Check auth status
|
||||
yarn twenty remote switch # Switch default remote
|
||||
yarn twenty remote list # List all configured remotes
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript", "import", "unicorn"],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "dist", "generated"],
|
||||
"rules": {
|
||||
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
|
||||
"no-console": "off",
|
||||
"no-control-regex": "off",
|
||||
"no-debugger": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-redeclare": "off",
|
||||
"import/no-duplicates": "error",
|
||||
"typescript/no-redeclare": "error",
|
||||
"typescript/ban-ts-comment": "error",
|
||||
"typescript/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
"prefer": "type-imports",
|
||||
"fixStyle": "inline-type-imports"
|
||||
}
|
||||
],
|
||||
"typescript/explicit-function-return-type": "off",
|
||||
"typescript/explicit-module-boundary-types": "off",
|
||||
"typescript/no-empty-object-type": [
|
||||
"error",
|
||||
{
|
||||
"allowInterfaces": "with-single-extends"
|
||||
}
|
||||
],
|
||||
"typescript/no-empty-function": "off",
|
||||
"typescript/no-explicit-any": "off",
|
||||
"typescript/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
generated
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"name": "twenty-client-sdk",
|
||||
"version": "0.8.0-canary.8",
|
||||
"sideEffects": false,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"build": "npx rimraf dist && npx vite build && tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist"
|
||||
},
|
||||
"exports": {
|
||||
"./core": {
|
||||
"types": "./dist/core/index.d.ts",
|
||||
"import": "./dist/core.mjs",
|
||||
"require": "./dist/core.cjs"
|
||||
},
|
||||
"./metadata": {
|
||||
"types": "./dist/metadata/index.d.ts",
|
||||
"import": "./dist/metadata.mjs",
|
||||
"require": "./dist/metadata.cjs"
|
||||
},
|
||||
"./generate": {
|
||||
"types": "./dist/generate/index.d.ts",
|
||||
"import": "./dist/generate.mjs",
|
||||
"require": "./dist/generate.cjs"
|
||||
}
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"core": [
|
||||
"dist/core/index.d.ts"
|
||||
],
|
||||
"metadata": [
|
||||
"dist/metadata/index.d.ts"
|
||||
],
|
||||
"generate": [
|
||||
"dist/generate/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@genql/cli": "^3.0.3",
|
||||
"@genql/runtime": "^2.10.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"graphql": "^16.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"twenty-shared": "workspace:*",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"yarn": "^4.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "twenty-client-sdk",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/twenty-client-sdk/src",
|
||||
"projectType": "library",
|
||||
"tags": ["scope:sdk"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": true,
|
||||
"inputs": ["production", "^production"],
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["{projectRoot}/dist"],
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"commands": [
|
||||
"npx rimraf dist && npx vite build",
|
||||
"tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist"
|
||||
],
|
||||
"parallel": false
|
||||
}
|
||||
},
|
||||
"generate-metadata-client": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": false,
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["{projectRoot}/src/metadata/generated"],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-client-sdk",
|
||||
"command": "tsx scripts/generate-metadata-client.ts"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/vitest:test",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"config": "{projectRoot}/vitest.config.ts"
|
||||
}
|
||||
},
|
||||
"set-local-version": {},
|
||||
"typecheck": {},
|
||||
"lint": {}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { getIntrospectionQuery, buildClientSchema, printSchema } from 'graphql';
|
||||
|
||||
import { generateMetadataClient } from '../src/generate/generate-metadata-client';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const TEMPLATE_PATH = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'src',
|
||||
'generate',
|
||||
'twenty-client-template.ts',
|
||||
);
|
||||
|
||||
const introspectSchema = async (url: string): Promise<string> => {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query: getIntrospectionQuery() }),
|
||||
});
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
if (json.errors) {
|
||||
throw new Error(
|
||||
`GraphQL introspection errors: ${JSON.stringify(json.errors)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return printSchema(buildClientSchema(json.data));
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const serverUrl = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
const schema = await introspectSchema(`${serverUrl}/metadata`);
|
||||
|
||||
const clientWrapperTemplateSource = await readFile(TEMPLATE_PATH, 'utf-8');
|
||||
|
||||
const outputPath = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'src',
|
||||
'metadata',
|
||||
'generated',
|
||||
);
|
||||
|
||||
await generateMetadataClient({
|
||||
schema,
|
||||
outputPath,
|
||||
clientWrapperTemplateSource,
|
||||
});
|
||||
|
||||
console.log(`Metadata client generated at ${outputPath}`);
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Failed to generate metadata client:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
// Stub — replaced at runtime by the generated client when the app
|
||||
// is installed on a Twenty instance or during `twenty app:dev`.
|
||||
export class CoreApiClient {
|
||||
constructor() {
|
||||
throw new Error(
|
||||
'CoreApiClient was not generated. ' +
|
||||
'Install this app on a Twenty instance or run `twenty app:dev`.',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { CoreApiClient } from './generated/index';
|
||||
export * as CoreSchema from './generated/schema';
|
||||
@@ -1,51 +0,0 @@
|
||||
type ClientWrapperOptions = {
|
||||
apiClientName: string;
|
||||
defaultUrl: string;
|
||||
includeUploadFile: boolean;
|
||||
};
|
||||
|
||||
const STRIPPED_TYPES_START = '// __STRIPPED_DURING_INJECTION_START__';
|
||||
const STRIPPED_TYPES_END = '// __STRIPPED_DURING_INJECTION_END__';
|
||||
const UPLOAD_FILE_START = '// __UPLOAD_FILE_START__';
|
||||
const UPLOAD_FILE_END = '// __UPLOAD_FILE_END__';
|
||||
|
||||
const escapeRegExp = (value: string): string =>
|
||||
value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
export const buildClientWrapperSource = (
|
||||
templateSource: string,
|
||||
options: ClientWrapperOptions,
|
||||
): string => {
|
||||
let source = templateSource;
|
||||
|
||||
source = source.replace(
|
||||
new RegExp(
|
||||
`${escapeRegExp(STRIPPED_TYPES_START)}[\\s\\S]*?${escapeRegExp(STRIPPED_TYPES_END)}\\n?`,
|
||||
),
|
||||
'',
|
||||
);
|
||||
|
||||
source = source.replace("'__TWENTY_DEFAULT_URL__'", options.defaultUrl);
|
||||
|
||||
source = source.replace(/TwentyGeneratedClient/g, options.apiClientName);
|
||||
|
||||
if (!options.includeUploadFile) {
|
||||
source = source.replace(
|
||||
new RegExp(
|
||||
`\\s*${escapeRegExp(UPLOAD_FILE_START)}[\\s\\S]*?${escapeRegExp(UPLOAD_FILE_END)}\\n?`,
|
||||
),
|
||||
'\n',
|
||||
);
|
||||
} else {
|
||||
source = source.replace(
|
||||
new RegExp(`\\s*${escapeRegExp(UPLOAD_FILE_START)}\\n`),
|
||||
'\n',
|
||||
);
|
||||
source = source.replace(
|
||||
new RegExp(`\\s*${escapeRegExp(UPLOAD_FILE_END)}\\n`),
|
||||
'\n',
|
||||
);
|
||||
}
|
||||
|
||||
return `\n// ${options.apiClientName} (auto-injected by twenty-client-sdk)\n${source}`;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import { cp, mkdir, readdir, rename as fsRename, rm } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
export const ensureDir = (dirPath: string) =>
|
||||
mkdir(dirPath, { recursive: true });
|
||||
|
||||
export const emptyDir = async (dirPath: string): Promise<void> => {
|
||||
let entries: string[];
|
||||
|
||||
try {
|
||||
entries = await readdir(dirPath);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
||||
await mkdir(dirPath, { recursive: true });
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
entries.map((entry) =>
|
||||
rm(join(dirPath, entry), { recursive: true, force: true }),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const move = async (src: string, dest: string): Promise<void> => {
|
||||
try {
|
||||
await fsRename(src, dest);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && 'code' in error && error.code === 'EXDEV') {
|
||||
await cp(src, dest, { recursive: true });
|
||||
await rm(src, { recursive: true, force: true });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const remove = (filePath: string) =>
|
||||
rm(filePath, { recursive: true, force: true });
|
||||
@@ -1,122 +0,0 @@
|
||||
import { appendFile, copyFile, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { generate } from '@genql/cli';
|
||||
import { build } from 'esbuild';
|
||||
import { DEFAULT_API_URL_NAME } from 'twenty-shared/application';
|
||||
|
||||
import { buildClientWrapperSource } from './client-wrapper';
|
||||
import { emptyDir, ensureDir, move, remove } from './fs-utils';
|
||||
import twentyClientTemplateSource from './twenty-client-template.ts?raw';
|
||||
|
||||
const COMMON_SCALAR_TYPES = {
|
||||
DateTime: 'string',
|
||||
JSON: 'Record<string, unknown>',
|
||||
UUID: 'string',
|
||||
};
|
||||
|
||||
export const GENERATED_CORE_DIR = 'core/generated';
|
||||
|
||||
// Generates the core API client from a GraphQL schema string.
|
||||
// Produces both TypeScript source and compiled ESM/CJS bundles.
|
||||
export const generateCoreClientFromSchema = async ({
|
||||
schema,
|
||||
outputPath,
|
||||
clientWrapperTemplateSource,
|
||||
}: {
|
||||
schema: string;
|
||||
outputPath: string;
|
||||
clientWrapperTemplateSource?: string;
|
||||
}): Promise<void> => {
|
||||
const templateSource =
|
||||
clientWrapperTemplateSource ?? twentyClientTemplateSource;
|
||||
const tempPath = `${outputPath}.tmp`;
|
||||
|
||||
await ensureDir(tempPath);
|
||||
await emptyDir(tempPath);
|
||||
|
||||
try {
|
||||
await generate({
|
||||
schema,
|
||||
output: tempPath,
|
||||
scalarTypes: COMMON_SCALAR_TYPES,
|
||||
});
|
||||
|
||||
const clientContent = buildClientWrapperSource(templateSource, {
|
||||
apiClientName: 'CoreApiClient',
|
||||
defaultUrl: `\`\${process.env.${DEFAULT_API_URL_NAME}}/graphql\``,
|
||||
includeUploadFile: true,
|
||||
});
|
||||
|
||||
await appendFile(join(tempPath, 'index.ts'), clientContent);
|
||||
|
||||
await remove(outputPath);
|
||||
await move(tempPath, outputPath);
|
||||
|
||||
await compileGeneratedClient(outputPath);
|
||||
} catch (error) {
|
||||
await remove(tempPath);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Generates the core client and replaces the pre-built stub inside
|
||||
// an installed twenty-client-sdk package (dist/core.mjs and dist/core.cjs).
|
||||
// Generated source files are kept in dist/generated-core/ for consumers
|
||||
// that need the raw .ts files (e.g. the app:dev upload step).
|
||||
export const replaceCoreClient = async ({
|
||||
packageRoot,
|
||||
schema,
|
||||
}: {
|
||||
packageRoot: string;
|
||||
schema: string;
|
||||
}): Promise<void> => {
|
||||
const generatedPath = join(packageRoot, 'dist', GENERATED_CORE_DIR);
|
||||
|
||||
await generateCoreClientFromSchema({ schema, outputPath: generatedPath });
|
||||
|
||||
await copyFile(
|
||||
join(generatedPath, 'index.mjs'),
|
||||
join(packageRoot, 'dist', 'core.mjs'),
|
||||
);
|
||||
await copyFile(
|
||||
join(generatedPath, 'index.cjs'),
|
||||
join(packageRoot, 'dist', 'core.cjs'),
|
||||
);
|
||||
};
|
||||
|
||||
const compileGeneratedClient = async (generatedDir: string): Promise<void> => {
|
||||
const entryPoint = join(generatedDir, 'index.ts');
|
||||
const outfile = join(generatedDir, 'index.mjs');
|
||||
|
||||
await build({
|
||||
entryPoints: [entryPoint],
|
||||
outfile,
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
platform: 'node',
|
||||
target: 'node18',
|
||||
sourcemap: false,
|
||||
minify: false,
|
||||
});
|
||||
|
||||
await build({
|
||||
entryPoints: [entryPoint],
|
||||
outfile: join(generatedDir, 'index.cjs'),
|
||||
bundle: true,
|
||||
format: 'cjs',
|
||||
platform: 'node',
|
||||
target: 'node18',
|
||||
sourcemap: false,
|
||||
minify: false,
|
||||
});
|
||||
|
||||
await writeFile(
|
||||
join(generatedDir, 'package.json'),
|
||||
JSON.stringify(
|
||||
{ type: 'module', main: 'index.mjs', module: 'index.mjs' },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import { appendFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { generate } from '@genql/cli';
|
||||
import { DEFAULT_API_URL_NAME } from 'twenty-shared/application';
|
||||
|
||||
import { buildClientWrapperSource } from './client-wrapper';
|
||||
import { emptyDir, ensureDir } from './fs-utils';
|
||||
import twentyClientTemplateSource from './twenty-client-template.ts?raw';
|
||||
|
||||
const COMMON_SCALAR_TYPES = {
|
||||
DateTime: 'string',
|
||||
JSON: 'Record<string, unknown>',
|
||||
UUID: 'string',
|
||||
};
|
||||
|
||||
export const generateMetadataClient = async ({
|
||||
schema,
|
||||
outputPath,
|
||||
clientWrapperTemplateSource,
|
||||
}: {
|
||||
schema: string;
|
||||
outputPath: string;
|
||||
clientWrapperTemplateSource?: string;
|
||||
}): Promise<void> => {
|
||||
const templateSource =
|
||||
clientWrapperTemplateSource ?? twentyClientTemplateSource;
|
||||
|
||||
await ensureDir(outputPath);
|
||||
await emptyDir(outputPath);
|
||||
|
||||
await generate({
|
||||
schema,
|
||||
output: outputPath,
|
||||
scalarTypes: {
|
||||
...COMMON_SCALAR_TYPES,
|
||||
Upload: 'File',
|
||||
},
|
||||
});
|
||||
|
||||
const clientContent = buildClientWrapperSource(templateSource, {
|
||||
apiClientName: 'MetadataApiClient',
|
||||
defaultUrl: `\`\${process.env.${DEFAULT_API_URL_NAME}}/metadata\``,
|
||||
includeUploadFile: true,
|
||||
});
|
||||
|
||||
await appendFile(join(outputPath, 'index.ts'), clientContent);
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
export {
|
||||
GENERATED_CORE_DIR,
|
||||
generateCoreClientFromSchema,
|
||||
replaceCoreClient,
|
||||
} from './generate-core-client';
|
||||
export { generateMetadataClient } from './generate-metadata-client';
|
||||
@@ -1,4 +0,0 @@
|
||||
declare module '*.ts?raw' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
// Ambient type stubs for the genql-generated code this template gets
|
||||
// injected into. They enable full typecheck/lint on this file.
|
||||
// __STRIPPED_DURING_INJECTION_START__
|
||||
type QueryGenqlSelection = Record<string, unknown>;
|
||||
type MutationGenqlSelection = Record<string, unknown>;
|
||||
type GraphqlOperation = Record<string, unknown>;
|
||||
|
||||
type ClientOptions = {
|
||||
url?: string;
|
||||
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
||||
fetcher?: (
|
||||
operation: GraphqlOperation | GraphqlOperation[],
|
||||
) => Promise<unknown>;
|
||||
fetch?: typeof globalThis.fetch;
|
||||
batch?: unknown;
|
||||
};
|
||||
|
||||
type Client = {
|
||||
query: (
|
||||
request: QueryGenqlSelection & { __name?: string },
|
||||
) => Promise<unknown>;
|
||||
mutation: (
|
||||
request: MutationGenqlSelection & { __name?: string },
|
||||
) => Promise<unknown>;
|
||||
};
|
||||
|
||||
declare function createClient(options: ClientOptions): Client;
|
||||
|
||||
declare class GenqlError extends Error {
|
||||
constructor(errors: unknown, data: unknown);
|
||||
}
|
||||
// __STRIPPED_DURING_INJECTION_END__
|
||||
|
||||
const APP_ACCESS_TOKEN_ENV_KEY = 'TWENTY_APP_ACCESS_TOKEN';
|
||||
const API_KEY_ENV_KEY = 'TWENTY_API_KEY';
|
||||
|
||||
type TwentyGeneratedClientOptions = ClientOptions;
|
||||
|
||||
type ProcessEnvironment = Record<string, string | undefined>;
|
||||
|
||||
type GraphqlErrorPayloadEntry = {
|
||||
message?: string;
|
||||
extensions?: { code?: string };
|
||||
};
|
||||
|
||||
type GraphqlResponsePayload = {
|
||||
data?: Record<string, unknown>;
|
||||
errors?: GraphqlErrorPayloadEntry[];
|
||||
};
|
||||
|
||||
type GraphqlResponse = {
|
||||
status: number;
|
||||
statusText: string;
|
||||
payload: GraphqlResponsePayload | null;
|
||||
rawBody: string;
|
||||
};
|
||||
|
||||
const getProcessEnvironment = (): ProcessEnvironment => {
|
||||
const processObject = (
|
||||
globalThis as { process?: { env?: ProcessEnvironment } }
|
||||
).process;
|
||||
|
||||
return processObject?.env ?? {};
|
||||
};
|
||||
|
||||
const getTokenFromAuthorizationHeader = (
|
||||
authorizationHeader: string | undefined,
|
||||
): string | null => {
|
||||
if (typeof authorizationHeader !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmedAuthorizationHeader = authorizationHeader.trim();
|
||||
|
||||
if (trimmedAuthorizationHeader.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmedAuthorizationHeader === 'Bearer') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmedAuthorizationHeader.startsWith('Bearer ')) {
|
||||
return trimmedAuthorizationHeader.slice('Bearer '.length).trim();
|
||||
}
|
||||
|
||||
return trimmedAuthorizationHeader;
|
||||
};
|
||||
|
||||
const getTokenFromHeaders = (
|
||||
headers: HeadersInit | undefined,
|
||||
): string | null => {
|
||||
if (!headers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (headers instanceof Headers) {
|
||||
return getTokenFromAuthorizationHeader(
|
||||
headers.get('Authorization') ?? undefined,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(headers)) {
|
||||
const matchedAuthorizationHeader = headers.find(
|
||||
([headerName]) => headerName.toLowerCase() === 'authorization',
|
||||
);
|
||||
|
||||
return getTokenFromAuthorizationHeader(matchedAuthorizationHeader?.[1]);
|
||||
}
|
||||
|
||||
const headersRecord = headers as Record<string, string | undefined>;
|
||||
|
||||
return getTokenFromAuthorizationHeader(
|
||||
headersRecord.Authorization ?? headersRecord.authorization,
|
||||
);
|
||||
};
|
||||
|
||||
const hasAuthenticationErrorInGraphqlPayload = (
|
||||
payload: GraphqlResponsePayload | null,
|
||||
): boolean => {
|
||||
if (!payload?.errors) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload.errors.some((graphqlError) => {
|
||||
return (
|
||||
graphqlError.extensions?.code === 'UNAUTHENTICATED' ||
|
||||
graphqlError.message?.toLowerCase() === 'unauthorized'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const defaultOptions: TwentyGeneratedClientOptions = {
|
||||
url: '__TWENTY_DEFAULT_URL__',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
export class TwentyGeneratedClient {
|
||||
private client: Client;
|
||||
private url: string;
|
||||
private requestOptions: RequestInit;
|
||||
private headers: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
||||
private fetchImplementation: typeof globalThis.fetch | null;
|
||||
private authorizationToken: string | null;
|
||||
private refreshAccessTokenPromise: Promise<string | null> | null = null;
|
||||
|
||||
constructor(options?: TwentyGeneratedClientOptions) {
|
||||
const merged: TwentyGeneratedClientOptions = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
};
|
||||
|
||||
const {
|
||||
url,
|
||||
headers,
|
||||
fetch: customFetchImplementation,
|
||||
fetcher: _fetcher,
|
||||
batch: _batch,
|
||||
...requestOptions
|
||||
} = merged;
|
||||
|
||||
this.url = url ?? '';
|
||||
this.requestOptions = requestOptions;
|
||||
this.headers = headers ?? {};
|
||||
this.fetchImplementation =
|
||||
customFetchImplementation ?? globalThis.fetch ?? null;
|
||||
|
||||
const processEnvironment = getProcessEnvironment();
|
||||
const tokenFromHeaders = getTokenFromHeaders(
|
||||
typeof headers === 'function' ? undefined : headers,
|
||||
);
|
||||
|
||||
// Priority: explicit header > app access token > api key (legacy).
|
||||
this.authorizationToken =
|
||||
tokenFromHeaders ??
|
||||
processEnvironment[APP_ACCESS_TOKEN_ENV_KEY] ??
|
||||
processEnvironment[API_KEY_ENV_KEY] ??
|
||||
null;
|
||||
|
||||
this.client = createClient({
|
||||
...merged,
|
||||
headers: undefined,
|
||||
fetcher: async (operation) =>
|
||||
this.executeGraphqlRequestWithOptionalRefresh({
|
||||
operation,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
query<R extends QueryGenqlSelection>(request: R & { __name?: string }) {
|
||||
return this.client.query(request);
|
||||
}
|
||||
|
||||
mutation<R extends MutationGenqlSelection>(request: R & { __name?: string }) {
|
||||
return this.client.mutation(request);
|
||||
}
|
||||
|
||||
// __UPLOAD_FILE_START__
|
||||
async uploadFile(
|
||||
fileBuffer: Buffer,
|
||||
filename: string,
|
||||
contentType: string = 'application/octet-stream',
|
||||
fieldMetadataUniversalIdentifier: string,
|
||||
): Promise<{
|
||||
id: string;
|
||||
path: string;
|
||||
size: number;
|
||||
createdAt: string;
|
||||
url: string;
|
||||
}> {
|
||||
const form = new FormData();
|
||||
|
||||
form.append(
|
||||
'operations',
|
||||
JSON.stringify({
|
||||
query: `mutation UploadFilesFieldFileByUniversalIdentifier($file: Upload!, $fieldMetadataUniversalIdentifier: String!) {
|
||||
uploadFilesFieldFileByUniversalIdentifier(file: $file, fieldMetadataUniversalIdentifier: $fieldMetadataUniversalIdentifier) { id path size createdAt url }
|
||||
}`,
|
||||
variables: {
|
||||
file: null,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
},
|
||||
}),
|
||||
);
|
||||
form.append('map', JSON.stringify({ '0': ['variables.file'] }));
|
||||
form.append(
|
||||
'0',
|
||||
new Blob([fileBuffer as BlobPart], { type: contentType }),
|
||||
filename,
|
||||
);
|
||||
|
||||
const result = await this.executeGraphqlRequestWithOptionalRefresh({
|
||||
operation: form,
|
||||
headers: {},
|
||||
requestInit: {
|
||||
method: 'POST',
|
||||
},
|
||||
});
|
||||
|
||||
if (result.errors) {
|
||||
throw new GenqlError(result.errors, result.data);
|
||||
}
|
||||
|
||||
const data = result.data as Record<string, unknown>;
|
||||
|
||||
return data.uploadFilesFieldFileByUniversalIdentifier as {
|
||||
id: string;
|
||||
path: string;
|
||||
size: number;
|
||||
createdAt: string;
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
// __UPLOAD_FILE_END__
|
||||
|
||||
private async executeGraphqlRequestWithOptionalRefresh({
|
||||
operation,
|
||||
headers,
|
||||
requestInit,
|
||||
}: {
|
||||
operation: GraphqlOperation | GraphqlOperation[] | FormData;
|
||||
headers?: HeadersInit;
|
||||
requestInit?: RequestInit;
|
||||
}) {
|
||||
const firstResponse = await this.executeGraphqlRequest({
|
||||
operation,
|
||||
headers,
|
||||
requestInit,
|
||||
token: this.authorizationToken,
|
||||
});
|
||||
|
||||
if (this.shouldRefreshToken(firstResponse)) {
|
||||
const refreshedAccessToken = await this.requestRefreshedAccessToken();
|
||||
|
||||
if (refreshedAccessToken) {
|
||||
const retryResponse = await this.executeGraphqlRequest({
|
||||
operation,
|
||||
headers,
|
||||
requestInit,
|
||||
token: refreshedAccessToken,
|
||||
});
|
||||
|
||||
return this.assertResponseIsSuccessful(retryResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return this.assertResponseIsSuccessful(firstResponse);
|
||||
}
|
||||
|
||||
private async executeGraphqlRequest({
|
||||
operation,
|
||||
headers,
|
||||
requestInit,
|
||||
token,
|
||||
}: {
|
||||
operation: GraphqlOperation | GraphqlOperation[] | FormData;
|
||||
headers?: HeadersInit;
|
||||
requestInit?: RequestInit;
|
||||
token: string | null;
|
||||
}): Promise<GraphqlResponse> {
|
||||
if (!this.fetchImplementation) {
|
||||
throw new Error(
|
||||
'Global `fetch` function is not available, ' +
|
||||
'pass a fetch implementation to the Twenty client',
|
||||
);
|
||||
}
|
||||
|
||||
const resolvedHeaders = await this.resolveHeaders();
|
||||
const requestHeaders = new Headers(resolvedHeaders);
|
||||
|
||||
if (headers) {
|
||||
new Headers(headers).forEach((value, key) =>
|
||||
requestHeaders.set(key, value),
|
||||
);
|
||||
}
|
||||
|
||||
if (operation instanceof FormData) {
|
||||
requestHeaders.delete('Content-Type');
|
||||
} else {
|
||||
requestHeaders.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
if (token) {
|
||||
requestHeaders.set('Authorization', `Bearer ${token}`);
|
||||
} else {
|
||||
requestHeaders.delete('Authorization');
|
||||
}
|
||||
|
||||
const response = await this.fetchImplementation.call(globalThis, this.url, {
|
||||
...this.requestOptions,
|
||||
...requestInit,
|
||||
method: requestInit?.method ?? 'POST',
|
||||
headers: requestHeaders,
|
||||
body:
|
||||
operation instanceof FormData ? operation : JSON.stringify(operation),
|
||||
});
|
||||
|
||||
const rawBody = await response.text();
|
||||
let payload: GraphqlResponsePayload | null = null;
|
||||
|
||||
if (rawBody.trim().length > 0) {
|
||||
try {
|
||||
payload = JSON.parse(rawBody) as GraphqlResponsePayload;
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
payload,
|
||||
rawBody,
|
||||
};
|
||||
}
|
||||
|
||||
private async resolveHeaders(): Promise<HeadersInit> {
|
||||
if (typeof this.headers === 'function') {
|
||||
return (await this.headers()) ?? {};
|
||||
}
|
||||
|
||||
return this.headers ?? {};
|
||||
}
|
||||
|
||||
private shouldRefreshToken(response: GraphqlResponse): boolean {
|
||||
if (response.status === 401) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasAuthenticationErrorInGraphqlPayload(response.payload);
|
||||
}
|
||||
|
||||
private assertResponseIsSuccessful(response: GraphqlResponse) {
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
throw new Error(`${response.statusText}: ${response.rawBody}`);
|
||||
}
|
||||
|
||||
if (response.payload === null) {
|
||||
throw new Error('Invalid JSON response');
|
||||
}
|
||||
|
||||
return response.payload;
|
||||
}
|
||||
|
||||
private async requestRefreshedAccessToken(): Promise<string | null> {
|
||||
const refreshAccessTokenFunction = (
|
||||
globalThis as {
|
||||
frontComponentHostCommunicationApi?: {
|
||||
requestAccessTokenRefresh?: () => Promise<string>;
|
||||
};
|
||||
}
|
||||
).frontComponentHostCommunicationApi?.requestAccessTokenRefresh;
|
||||
|
||||
if (typeof refreshAccessTokenFunction !== 'function') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.refreshAccessTokenPromise) {
|
||||
this.refreshAccessTokenPromise = refreshAccessTokenFunction()
|
||||
.then((refreshedAccessToken) => {
|
||||
if (
|
||||
typeof refreshedAccessToken !== 'string' ||
|
||||
refreshedAccessToken.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.setAuthorizationToken(refreshedAccessToken);
|
||||
|
||||
return refreshedAccessToken;
|
||||
})
|
||||
.catch((refreshError: unknown) => {
|
||||
console.error('Twenty client: token refresh failed', refreshError);
|
||||
|
||||
return null;
|
||||
})
|
||||
.finally(() => {
|
||||
this.refreshAccessTokenPromise = null;
|
||||
});
|
||||
}
|
||||
|
||||
return this.refreshAccessTokenPromise;
|
||||
}
|
||||
|
||||
private setAuthorizationToken(token: string) {
|
||||
this.authorizationToken = token;
|
||||
|
||||
const processEnvironment = getProcessEnvironment();
|
||||
|
||||
processEnvironment[APP_ACCESS_TOKEN_ENV_KEY] = token;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { MetadataApiClient } from './generated/index';
|
||||
export * as MetadataSchema from './generated/schema';
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"lib": ["es2020", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strictNullChecks": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": false,
|
||||
"noEmit": true,
|
||||
"types": ["node"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "vite.config.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": false,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import path from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import packageJson from './package.json';
|
||||
|
||||
const entries = [
|
||||
'src/core/index.ts',
|
||||
'src/metadata/index.ts',
|
||||
'src/generate/index.ts',
|
||||
];
|
||||
|
||||
const entryFileNames = (chunk: any, extension: 'cjs' | 'mjs') => {
|
||||
if (!chunk.isEntry) {
|
||||
throw new Error(
|
||||
`Should never occur, encountered a non entry chunk ${chunk.facadeModuleId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const splitFaceModuleId = chunk.facadeModuleId?.split('/');
|
||||
if (splitFaceModuleId === undefined) {
|
||||
throw new Error(
|
||||
`Should never occur, splitFaceModuleId is undefined ${chunk.facadeModuleId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const moduleDirectory = splitFaceModuleId[splitFaceModuleId?.length - 2];
|
||||
if (moduleDirectory === 'src') {
|
||||
return `${chunk.name}.${extension}`;
|
||||
}
|
||||
return `${moduleDirectory}.${extension}`;
|
||||
};
|
||||
|
||||
export default defineConfig(() => {
|
||||
return {
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/packages/twenty-client-sdk',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@/': path.resolve(__dirname, 'src') + '/',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
root: __dirname,
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
outDir: 'dist',
|
||||
lib: { entry: entries, name: 'twenty-client-sdk' },
|
||||
rollupOptions: {
|
||||
external: [
|
||||
...Object.keys((packageJson as any).dependencies || {}),
|
||||
...Object.keys((packageJson as any).devDependencies || {}).filter(
|
||||
(dep: string) => dep !== 'twenty-shared',
|
||||
),
|
||||
'node:fs/promises',
|
||||
'node:fs',
|
||||
'node:path',
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: 'es',
|
||||
entryFileNames: (chunk) => entryFileNames(chunk, 'mjs'),
|
||||
},
|
||||
{
|
||||
format: 'cjs',
|
||||
interop: 'auto',
|
||||
esModule: true,
|
||||
exports: 'named',
|
||||
entryFileNames: (chunk) => entryFileNames(chunk, 'cjs'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
logLevel: 'warn',
|
||||
};
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
name: 'twenty-client-sdk',
|
||||
environment: 'node',
|
||||
include: [
|
||||
'src/**/__tests__/**/*.{test,spec}.{ts,tsx}',
|
||||
'src/**/*.{test,spec}.{ts,tsx}',
|
||||
],
|
||||
exclude: ['**/node_modules/**', '**/.git/**'],
|
||||
globals: true,
|
||||
},
|
||||
});
|
||||
+12998
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
"description": "Twenty meeting recorder",
|
||||
"main": ".webpack/main",
|
||||
"scripts": {
|
||||
"start": "concurrently \"yarn start:server\" \"yarn start:electron\"",
|
||||
"start": "concurrently \"npm run start:server\" \"npm run start:electron\"",
|
||||
"start:electron": "electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
@@ -37,8 +37,10 @@
|
||||
"node-loader": "^2.1.0",
|
||||
"style-loader": "^3.3.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@electron/packager/@electron/osx-sign": "github:recallai/osx-sign"
|
||||
"overrides": {
|
||||
"@electron/packager": {
|
||||
"@electron/osx-sign": "github:recallai/osx-sign"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.40.1",
|
||||
|
||||
@@ -16,5 +16,3 @@ STORAGE_TYPE=local
|
||||
# STORAGE_S3_REGION=eu-west3
|
||||
# STORAGE_S3_NAME=my-bucket
|
||||
# STORAGE_S3_ENDPOINT=
|
||||
# STORAGE_S3_ACCESS_KEY_ID=
|
||||
# STORAGE_S3_SECRET_ACCESS_KEY=
|
||||
|
||||
@@ -18,7 +18,7 @@ DOCKER_NETWORK=twenty_network
|
||||
# =============================================================================
|
||||
|
||||
prod-build:
|
||||
@cd ../.. && docker build --target twenty -f ./packages/twenty-docker/twenty/Dockerfile --platform $(PLATFORM) --tag twenty:$(TAG) . && cd -
|
||||
@cd ../.. && docker build -f ./packages/twenty-docker/twenty/Dockerfile --platform $(PLATFORM) --tag twenty:$(TAG) . && cd -
|
||||
|
||||
prod-run:
|
||||
@docker run -d -p 3000:3000 --name twenty twenty:$(TAG)
|
||||
|
||||
@@ -89,15 +89,6 @@ password
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Check if using external secret for redis password */}}
|
||||
{{- define "twenty.redis.useExternalSecret" -}}
|
||||
{{- if and (not .Values.redisInternal.enabled) .Values.redis.external.secretName .Values.redis.external.passwordKey -}}
|
||||
true
|
||||
{{- else -}}
|
||||
false
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Compose Redis URL */}}
|
||||
{{- define "twenty.redisUrl" -}}
|
||||
{{- if .Values.server.env.REDIS_URL -}}
|
||||
@@ -108,14 +99,9 @@ false
|
||||
{{- else -}}
|
||||
{{- $host := .Values.redis.external.host | default "redis" -}}
|
||||
{{- $port := .Values.redis.external.port | default 6379 -}}
|
||||
{{- if or (eq (include "twenty.redis.useExternalSecret" .) "true") (.Values.redis.external.password) -}}
|
||||
{{- $auth := ":$(REDIS_PASSWORD)@" -}}
|
||||
{{- printf "redis://%s%s:%v" $auth $host $port -}}
|
||||
{{- else -}}
|
||||
{{- printf "redis://%s:%v" $host $port -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Compose Server URL from override, ingress, or service */}}
|
||||
{{- define "twenty.serverUrl" -}}
|
||||
|
||||
@@ -46,12 +46,12 @@ spec:
|
||||
volumeMounts:
|
||||
- name: redis-data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: redis-data
|
||||
{{- if .Values.redisInternal.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.redisInternal.persistence.existingClaim | default (printf "%s-redis" (include "twenty.fullname" .)) }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: redis-data
|
||||
{{- if .Values.redisInternal.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.redisInternal.persistence.existingClaim | default (printf "%s-redis" (include "twenty.fullname" .)) }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -83,16 +83,16 @@ spec:
|
||||
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v db="${DBNAME}" -Atc "SELECT 1 FROM pg_database WHERE datname = :'db'" | grep -q 1 || \
|
||||
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v db="${DBNAME}" -c 'CREATE DATABASE :"db";'
|
||||
echo "Creating app user ${APP_USER} if it doesn't exist..."
|
||||
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v app_user="${APP_USER}" -v app_password="${APP_PASSWORD}" <<'EOSQL'
|
||||
DO
|
||||
$do$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'app_user') THEN
|
||||
EXECUTE format('CREATE USER %I WITH PASSWORD %L', :'app_user', :'app_password');
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
EOSQL
|
||||
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v app_user="${APP_USER}" -v app_password="${APP_PASSWORD}" <<'EOSQL'
|
||||
DO
|
||||
$do$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'app_user') THEN
|
||||
EXECUTE format('CREATE USER %I WITH PASSWORD %L', :'app_user', :'app_password');
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
EOSQL
|
||||
echo "Creating core schema and granting permissions..."
|
||||
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'CREATE SCHEMA IF NOT EXISTS core'
|
||||
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v db="${DBNAME}" -v app_user="${APP_USER}" -c 'GRANT ALL PRIVILEGES ON DATABASE :"db" TO :"app_user";'
|
||||
@@ -106,6 +106,31 @@ spec:
|
||||
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO :"app_user";'
|
||||
echo "Database ${DBNAME} is ready."
|
||||
{{- end }}
|
||||
- name: run-migrations
|
||||
{{- $img := include "twenty.server.image" . }}
|
||||
image: {{ include "twenty.image.repository" $img }}:{{ include "twenty.image.tag" $img }}
|
||||
imagePullPolicy: {{ include "twenty.image.pullPolicy" $img }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- >-
|
||||
npx -y typeorm migration:run -d dist/database/typeorm/core/core.datasource
|
||||
env:
|
||||
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.dbPassword.secretName" . }}
|
||||
key: {{ include "twenty.dbPassword.secretKey" . }}
|
||||
- name: PG_DATABASE_URL
|
||||
value: {{ include "twenty.dbUrl.template" . | quote }}
|
||||
{{- else }}
|
||||
- name: PG_DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "twenty.dbUrl.secretName" . }}
|
||||
key: url
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: server
|
||||
{{- $img := include "twenty.server.image" . }}
|
||||
@@ -129,16 +154,6 @@ spec:
|
||||
name: {{ include "twenty.dbUrl.secretName" . }}
|
||||
key: url
|
||||
{{- end }}
|
||||
{{- if eq (include "twenty.redis.useExternalSecret" .) "true" }}
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.redis.external.secretName }}
|
||||
key: {{ .Values.redis.external.passwordKey }}
|
||||
{{- else if .Values.redis.external.password }}
|
||||
- name: REDIS_PASSWORD
|
||||
value: {{ .Values.redis.external.password | quote }}
|
||||
{{- end }}
|
||||
- name: REDIS_URL
|
||||
value: {{ include "twenty.redisUrl" . | quote }}
|
||||
- name: SIGN_IN_PREFILLED
|
||||
@@ -156,10 +171,7 @@ spec:
|
||||
key: accessToken
|
||||
{{- $storageEnv := (include "twenty.storageEnv" .) }}
|
||||
{{- if $storageEnv }}
|
||||
{{- $storageEnv | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.server.extraEnv }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{ $storageEnv | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http-tcp
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user