mirror of
https://github.com/twentyhq/twenty.git
synced 2026-04-18 14:01:45 -04:00
feat(sdk): use config file as single source of truth, remove env var fallbacks (#19409)
## Summary - **Config as source of truth**: `~/.twenty/config.json` is now the single source of truth for SDK authentication — env var fallbacks have been removed from the config resolution chain. - **Test instance support**: `twenty server start --test` spins up a dedicated Docker instance on port 2021 with its own config (`config.test.json`), so integration tests don't interfere with the dev environment. - **API key auth for marketplace**: Removed `UserAuthGuard` from `MarketplaceResolver` so API key tokens (workspace-scoped) can call `installMarketplaceApp`. - **CI for example apps**: Added monorepo CI workflows for `hello-world` and `postcard` example apps to catch regressions. - **Simplified CI**: All `ci-create-app-e2e` and example app workflows now use a shared `spawn-twenty-app-dev-test` action (Docker-based) instead of building the server from source. Consolidated auth env vars to `TWENTY_API_URL` + `TWENTY_API_KEY`. - **Template publishing fix**: `create-twenty-app` template now correctly preserves `.github/` and `.gitignore` through npm publish (stored without leading dot, renamed after copy). ## Test plan - [x] CI SDK (lint, typecheck, unit, integration, e2e) — all green - [x] CI Example App Hello World — green - [x] CI Example App Postcard — green - [x] CI Create App E2E minimal — green - [x] CI Front, CI Server, CI Shared — green
This commit is contained in:
47
.github/actions/spawn-twenty-app-dev-test/action.yml
vendored
Normal file
47
.github/actions/spawn-twenty-app-dev-test/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Spawn Twenty App Dev Test
|
||||
description: >
|
||||
Starts a Twenty all-in-one test instance (server, worker, database, redis)
|
||||
using the twentycrm/twenty-app-dev Docker image on port 2021.
|
||||
The server is available at http://localhost:2021 with seeded demo data.
|
||||
|
||||
inputs:
|
||||
twenty-version:
|
||||
description: 'Twenty Docker Hub image tag for twenty-app-dev (e.g., "latest" or "v1.20.0").'
|
||||
required: false
|
||||
default: 'latest'
|
||||
|
||||
outputs:
|
||||
server-url:
|
||||
description: 'URL where the Twenty test server can be reached'
|
||||
value: http://localhost:2021
|
||||
api-key:
|
||||
description: 'API key for the Twenty test instance'
|
||||
value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ1c2VySWQiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjQ5MDQ4ODE3MDR9.9S4wc0MOr5iczsomlFxZdOHD1IRDS4dnRSwNVNpctF4
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Start twenty-app-dev-test container
|
||||
shell: bash
|
||||
run: |
|
||||
docker run -d \
|
||||
--name twenty-app-dev-test \
|
||||
-p 2021:2021 \
|
||||
-e NODE_PORT=2021 \
|
||||
-e SERVER_URL=http://localhost:2021 \
|
||||
twentycrm/twenty-app-dev:${{ inputs.twenty-version }}
|
||||
|
||||
echo "Waiting for Twenty test instance to become healthy…"
|
||||
TIMEOUT=180
|
||||
ELAPSED=0
|
||||
until curl -sf http://localhost:2021/healthz > /dev/null 2>&1; do
|
||||
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
|
||||
echo "::error::Twenty did not become healthy within ${TIMEOUT}s"
|
||||
docker logs twenty-app-dev-test 2>&1 | tail -80
|
||||
exit 1
|
||||
fi
|
||||
sleep 3
|
||||
ELAPSED=$((ELAPSED + 3))
|
||||
echo " … waited ${ELAPSED}s"
|
||||
done
|
||||
echo "Twenty test instance is ready at http://localhost:2021 (took ~${ELAPSED}s)"
|
||||
@@ -25,34 +25,15 @@ jobs:
|
||||
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-hello-world:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
PUBLISHABLE_PACKAGES: twenty-client-sdk twenty-sdk create-twenty-app
|
||||
steps:
|
||||
@@ -139,30 +120,14 @@ jobs:
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty --version
|
||||
|
||||
- name: Setup server environment
|
||||
run: npx nx reset:env:e2e-testing-server twenty-server
|
||||
|
||||
- name: Build server
|
||||
run: npx nx build twenty-server
|
||||
|
||||
- name: Create and setup database
|
||||
run: |
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
npx nx run twenty-server:database:reset
|
||||
|
||||
- name: Start server
|
||||
run: |
|
||||
npx nx start twenty-server &
|
||||
echo "Waiting for server to be ready..."
|
||||
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: ./.github/actions/spawn-twenty-app-dev-test
|
||||
|
||||
- name: Authenticate with twenty-server
|
||||
env:
|
||||
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 --api-key ${{ steps.twenty.outputs.api-key }} --api-url ${{ steps.twenty.outputs.server-url }}
|
||||
|
||||
- name: Deploy scaffolded app
|
||||
run: |
|
||||
@@ -190,7 +155,8 @@ jobs:
|
||||
|
||||
- name: Run scaffolded app integration test
|
||||
env:
|
||||
TWENTY_API_URL: http://localhost:3000
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
yarn test
|
||||
|
||||
46
.github/workflows/ci-create-app-e2e-minimal.yaml
vendored
46
.github/workflows/ci-create-app-e2e-minimal.yaml
vendored
@@ -23,34 +23,15 @@ jobs:
|
||||
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-minimal:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
PUBLISHABLE_PACKAGES: twenty-client-sdk twenty-sdk create-twenty-app
|
||||
steps:
|
||||
@@ -133,30 +114,14 @@ jobs:
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty --version
|
||||
|
||||
- name: Setup server environment
|
||||
run: npx nx reset:env:e2e-testing-server twenty-server
|
||||
|
||||
- name: Build server
|
||||
run: npx nx build twenty-server
|
||||
|
||||
- name: Create and setup database
|
||||
run: |
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
npx nx run twenty-server:database:reset
|
||||
|
||||
- name: Start server
|
||||
run: |
|
||||
npx nx start twenty-server &
|
||||
echo "Waiting for server to be ready..."
|
||||
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: ./.github/actions/spawn-twenty-app-dev-test
|
||||
|
||||
- name: Authenticate with twenty-server
|
||||
env:
|
||||
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 --api-key ${{ steps.twenty.outputs.api-key }} --api-url ${{ steps.twenty.outputs.server-url }}
|
||||
|
||||
- name: Deploy scaffolded app
|
||||
run: |
|
||||
@@ -170,7 +135,8 @@ jobs:
|
||||
|
||||
- name: Run scaffolded app integration test
|
||||
env:
|
||||
TWENTY_API_URL: http://localhost:3000
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
yarn test
|
||||
|
||||
@@ -25,34 +25,15 @@ jobs:
|
||||
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-postcard:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-4-cores
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
PUBLISHABLE_PACKAGES: twenty-client-sdk twenty-sdk create-twenty-app
|
||||
steps:
|
||||
@@ -137,30 +118,14 @@ jobs:
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
npx --no-install twenty --version
|
||||
|
||||
- name: Setup server environment
|
||||
run: npx nx reset:env:e2e-testing-server twenty-server
|
||||
|
||||
- name: Build server
|
||||
run: npx nx build twenty-server
|
||||
|
||||
- name: Create and setup database
|
||||
run: |
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
npx nx run twenty-server:database:reset
|
||||
|
||||
- name: Start server
|
||||
run: |
|
||||
npx nx start twenty-server &
|
||||
echo "Waiting for server to be ready..."
|
||||
timeout 60 bash -c 'until curl -s http://localhost:3000/health; do sleep 2; done'
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: ./.github/actions/spawn-twenty-app-dev-test
|
||||
|
||||
- name: Authenticate with twenty-server
|
||||
env:
|
||||
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 --api-key ${{ steps.twenty.outputs.api-key }} --api-url ${{ steps.twenty.outputs.server-url }}
|
||||
|
||||
- name: Deploy scaffolded app
|
||||
run: |
|
||||
@@ -188,7 +153,8 @@ jobs:
|
||||
|
||||
- name: Run scaffolded app integration test
|
||||
env:
|
||||
TWENTY_API_URL: http://localhost:3000
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
run: |
|
||||
cd /tmp/e2e-test-workspace/test-app
|
||||
yarn test
|
||||
|
||||
64
.github/workflows/ci-example-app-hello-world.yaml
vendored
Normal file
64
.github/workflows/ci-example-app-hello-world.yaml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: CI Example App Hello World
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
packages/twenty-apps/examples/hello-world/**
|
||||
packages/twenty-sdk/**
|
||||
packages/twenty-client-sdk/**
|
||||
packages/twenty-shared/**
|
||||
!packages/twenty-sdk/package.json
|
||||
!packages/twenty-client-sdk/package.json
|
||||
!packages/twenty-shared/package.json
|
||||
|
||||
example-app-hello-world:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Build SDK packages
|
||||
run: npx nx build twenty-sdk
|
||||
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: ./.github/actions/spawn-twenty-app-dev-test
|
||||
|
||||
- name: Run integration tests
|
||||
working-directory: packages/twenty-apps/examples/hello-world
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
run: npx vitest run
|
||||
|
||||
ci-example-app-hello-world-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, example-app-hello-world]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
64
.github/workflows/ci-example-app-postcard.yaml
vendored
Normal file
64
.github/workflows/ci-example-app-postcard.yaml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: CI Example App Postcard
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
packages/twenty-apps/examples/postcard/**
|
||||
packages/twenty-sdk/**
|
||||
packages/twenty-client-sdk/**
|
||||
packages/twenty-shared/**
|
||||
!packages/twenty-sdk/package.json
|
||||
!packages/twenty-client-sdk/package.json
|
||||
!packages/twenty-shared/package.json
|
||||
|
||||
example-app-postcard:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Build SDK packages
|
||||
run: npx nx build twenty-sdk
|
||||
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: ./.github/actions/spawn-twenty-app-dev-test
|
||||
|
||||
- name: Run integration tests
|
||||
working-directory: packages/twenty-apps/examples/postcard
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
run: npx vitest run
|
||||
|
||||
ci-example-app-postcard-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, example-app-postcard]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
run: exit 1
|
||||
2
.github/workflows/ci-sdk.yaml
vendored
2
.github/workflows/ci-sdk.yaml
vendored
@@ -70,6 +70,8 @@ jobs:
|
||||
- 6379:6379
|
||||
env:
|
||||
NODE_ENV: test
|
||||
TWENTY_API_URL: http://localhost:3000
|
||||
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -6,9 +6,16 @@ on:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,12 +23,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@feature/sdk-config-file-source-of-truth
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
@@ -30,7 +36,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
@@ -39,4 +45,4 @@ jobs:
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
@@ -3,43 +3,51 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
|
||||
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.test.json');
|
||||
|
||||
beforeAll(async () => {
|
||||
const apiUrl = process.env.TWENTY_API_URL!;
|
||||
const token = process.env.TWENTY_API_KEY!;
|
||||
|
||||
if (!apiUrl || !token) {
|
||||
throw new Error(
|
||||
'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
|
||||
'Start a local server: yarn twenty server start\n' +
|
||||
'Or set them in vitest env config.',
|
||||
);
|
||||
}
|
||||
|
||||
const assertServerIsReachable = async () => {
|
||||
let response: Response;
|
||||
|
||||
try {
|
||||
response = await fetch(`${TWENTY_API_URL}/healthz`);
|
||||
response = await fetch(`${apiUrl}/healthz`);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
|
||||
`Twenty server is not reachable at ${apiUrl}. ` +
|
||||
'Make sure the server is running before executing integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server at ${TWENTY_API_URL} returned ${response.status}`);
|
||||
throw new Error(`Server at ${apiUrl} returned ${response.status}`);
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await assertServerIsReachable();
|
||||
|
||||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||||
|
||||
const configFile = {
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
};
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(TEST_CONFIG_DIR, 'config.json'),
|
||||
JSON.stringify(configFile, null, 2),
|
||||
CONFIG_PATH,
|
||||
JSON.stringify(
|
||||
{
|
||||
remotes: {
|
||||
local: { apiUrl, apiKey: token },
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
process.env.TWENTY_APP_ACCESS_TOKEN ??= token;
|
||||
});
|
||||
|
||||
@@ -14,8 +14,11 @@ export default defineConfig({
|
||||
include: ['src/**/*.integration-test.ts'],
|
||||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||||
env: {
|
||||
TWENTY_API_URL: process.env.TWENTY_API_URL ?? 'http://localhost:2020',
|
||||
TWENTY_API_KEY:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik',
|
||||
process.env.TWENTY_API_KEY ??
|
||||
// Tim Apple (admin) access token for twenty-app-dev
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ1c2VySWQiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjQ5MDQ4ODE3MDR9.9S4wc0MOr5iczsomlFxZdOHD1IRDS4dnRSwNVNpctF4',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export const copyBaseApplicationProject = async ({
|
||||
console.log(chalk.gray('Generating application project...'));
|
||||
await fs.copy(join(__dirname, './constants/template'), appDirectory);
|
||||
|
||||
await renameGitignore({ appDirectory });
|
||||
await renameDotfiles({ appDirectory });
|
||||
|
||||
await generateUniversalIdentifiers({
|
||||
appDisplayName,
|
||||
@@ -32,11 +32,20 @@ export const copyBaseApplicationProject = async ({
|
||||
await updatePackageJson({ appName, appDirectory });
|
||||
};
|
||||
|
||||
const renameGitignore = async ({ appDirectory }: { appDirectory: string }) => {
|
||||
const gitignorePath = join(appDirectory, 'gitignore');
|
||||
// npm strips dotfiles/dotdirs (.gitignore, .github/) from published packages,
|
||||
// so we store them without the leading dot and rename after copying.
|
||||
const renameDotfiles = async ({ appDirectory }: { appDirectory: string }) => {
|
||||
const renames = [
|
||||
{ from: 'gitignore', to: '.gitignore' },
|
||||
{ from: 'github', to: '.github' },
|
||||
];
|
||||
|
||||
if (await fs.pathExists(gitignorePath)) {
|
||||
await fs.rename(gitignorePath, join(appDirectory, '.gitignore'));
|
||||
for (const { from, to } of renames) {
|
||||
const sourcePath = join(appDirectory, from);
|
||||
|
||||
if (await fs.pathExists(sourcePath)) {
|
||||
await fs.rename(sourcePath, join(appDirectory, to));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,9 +6,16 @@ on:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,12 +23,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@feature/sdk-config-file-source-of-truth
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
@@ -30,7 +36,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
@@ -39,4 +45,4 @@ jobs:
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
|
||||
@@ -3,43 +3,51 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
|
||||
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.test.json');
|
||||
|
||||
beforeAll(async () => {
|
||||
const apiUrl = process.env.TWENTY_API_URL!;
|
||||
const token = process.env.TWENTY_API_KEY!;
|
||||
|
||||
if (!apiUrl || !token) {
|
||||
throw new Error(
|
||||
'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
|
||||
'Start a local server: yarn twenty server start\n' +
|
||||
'Or set them in vitest env config.',
|
||||
);
|
||||
}
|
||||
|
||||
const assertServerIsReachable = async () => {
|
||||
let response: Response;
|
||||
|
||||
try {
|
||||
response = await fetch(`${TWENTY_API_URL}/healthz`);
|
||||
response = await fetch(`${apiUrl}/healthz`);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
|
||||
`Twenty server is not reachable at ${apiUrl}. ` +
|
||||
'Make sure the server is running before executing integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server at ${TWENTY_API_URL} returned ${response.status}`);
|
||||
throw new Error(`Server at ${apiUrl} returned ${response.status}`);
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await assertServerIsReachable();
|
||||
|
||||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||||
|
||||
const configFile = {
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
};
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(TEST_CONFIG_DIR, 'config.json'),
|
||||
JSON.stringify(configFile, null, 2),
|
||||
CONFIG_PATH,
|
||||
JSON.stringify(
|
||||
{
|
||||
remotes: {
|
||||
local: { apiUrl, apiKey: token },
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
process.env.TWENTY_APP_ACCESS_TOKEN ??= token;
|
||||
});
|
||||
|
||||
@@ -14,9 +14,11 @@ export default defineConfig({
|
||||
include: ['src/**/*.integration-test.ts'],
|
||||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||||
env: {
|
||||
TWENTY_API_URL: 'http://localhost:3000',
|
||||
TWENTY_API_URL: process.env.TWENTY_API_URL ?? 'http://localhost:2020',
|
||||
TWENTY_API_KEY:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik',
|
||||
process.env.TWENTY_API_KEY ??
|
||||
// Tim Apple (admin) access token for twenty-app-dev
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ1c2VySWQiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjQ5MDQ4ODE3MDR9.9S4wc0MOr5iczsomlFxZdOHD1IRDS4dnRSwNVNpctF4',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,9 +6,16 @@ on:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,12 +23,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
- name: Spawn Twenty test instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@feature/sdk-config-file-source-of-truth
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
@@ -30,7 +36,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
@@ -39,4 +45,4 @@ jobs:
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
||||
|
||||
@@ -3,43 +3,51 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
|
||||
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.test.json');
|
||||
|
||||
beforeAll(async () => {
|
||||
const apiUrl = process.env.TWENTY_API_URL!;
|
||||
const token = process.env.TWENTY_API_KEY!;
|
||||
|
||||
if (!apiUrl || !token) {
|
||||
throw new Error(
|
||||
'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
|
||||
'Start a local server: yarn twenty server start\n' +
|
||||
'Or set them in vitest env config.',
|
||||
);
|
||||
}
|
||||
|
||||
const assertServerIsReachable = async () => {
|
||||
let response: Response;
|
||||
|
||||
try {
|
||||
response = await fetch(`${TWENTY_API_URL}/healthz`);
|
||||
response = await fetch(`${apiUrl}/healthz`);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
|
||||
`Twenty server is not reachable at ${apiUrl}. ` +
|
||||
'Make sure the server is running before executing integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server at ${TWENTY_API_URL} returned ${response.status}`);
|
||||
throw new Error(`Server at ${apiUrl} returned ${response.status}`);
|
||||
}
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await assertServerIsReachable();
|
||||
|
||||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||||
|
||||
const configFile = {
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
};
|
||||
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(TEST_CONFIG_DIR, 'config.json'),
|
||||
JSON.stringify(configFile, null, 2),
|
||||
CONFIG_PATH,
|
||||
JSON.stringify(
|
||||
{
|
||||
remotes: {
|
||||
local: { apiUrl, apiKey: token },
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
process.env.TWENTY_APP_ACCESS_TOKEN ??= token;
|
||||
});
|
||||
|
||||
@@ -14,8 +14,11 @@ export default defineConfig({
|
||||
include: ['src/**/*.integration-test.ts'],
|
||||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||||
env: {
|
||||
TWENTY_API_URL: process.env.TWENTY_API_URL ?? 'http://localhost:2020',
|
||||
TWENTY_API_KEY:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik',
|
||||
process.env.TWENTY_API_KEY ??
|
||||
// Tim Apple (admin) access token for twenty-app-dev
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ1c2VySWQiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjQ5MDQ4ODE3MDR9.9S4wc0MOr5iczsomlFxZdOHD1IRDS4dnRSwNVNpctF4',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import axios from 'axios';
|
||||
import { SERVER_URL } from '@/cli/__tests__/constants/server-url.constant';
|
||||
import { getServerUrl } from '@/cli/__tests__/constants/server-url.constant';
|
||||
|
||||
describe('Twenty Server Health Check (E2E)', () => {
|
||||
const HEALTH_ENDPOINT = `${SERVER_URL}/healthz`;
|
||||
let healthEndpoint: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const serverUrl = await getServerUrl();
|
||||
|
||||
healthEndpoint = `${serverUrl}/healthz`;
|
||||
});
|
||||
|
||||
it('should return 200 for health', async () => {
|
||||
const response = await axios.get(HEALTH_ENDPOINT);
|
||||
const response = await axios.get(healthEndpoint);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
export const SERVER_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export const getServerUrl = async (): Promise<string> => {
|
||||
const config = await new ConfigService().getConfig();
|
||||
|
||||
return config.apiUrl;
|
||||
};
|
||||
|
||||
@@ -5,20 +5,44 @@ import { beforeAll } from 'vitest';
|
||||
import { ensureDir } from '@/cli/utilities/file/fs-utils';
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
|
||||
const testConfigPath = getConfigPath();
|
||||
const testConfigPath = getConfigPath(true);
|
||||
|
||||
beforeAll(async () => {
|
||||
const apiUrl = process.env.TWENTY_API_URL;
|
||||
const token = process.env.TWENTY_API_KEY;
|
||||
|
||||
if (!apiUrl || !token) {
|
||||
throw new Error(
|
||||
'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
|
||||
'Run: twenty server start --test\n' +
|
||||
'Or set them in vitest env config.',
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/healthz`).catch(() => null);
|
||||
|
||||
if (!response?.ok) {
|
||||
throw new Error(
|
||||
`Twenty server not reachable at ${apiUrl}. ` +
|
||||
'Run: twenty server start --test',
|
||||
);
|
||||
}
|
||||
|
||||
await ensureDir(path.dirname(testConfigPath));
|
||||
|
||||
const configFile = {
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
await writeFile(
|
||||
testConfigPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
remotes: {
|
||||
local: { apiUrl, apiKey: token },
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
};
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
await writeFile(testConfigPath, JSON.stringify(configFile, null, 2));
|
||||
process.env.TWENTY_APP_ACCESS_TOKEN ??= token;
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { DevModeOrchestrator } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator';
|
||||
import { OrchestratorState } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator-state';
|
||||
@@ -29,9 +30,11 @@ export class AppDevCommand {
|
||||
|
||||
await checkSdkVersionCompatibility(appPath);
|
||||
|
||||
const config = await new ConfigService().getConfig();
|
||||
|
||||
const orchestratorState = new OrchestratorState({
|
||||
appPath,
|
||||
frontendUrl: process.env.FRONTEND_URL,
|
||||
frontendUrl: config.apiUrl,
|
||||
});
|
||||
|
||||
if (!options.headless) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authLogin } from '@/cli/operations/login';
|
||||
import { authLoginOAuth } from '@/cli/operations/login-oauth';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
import { detectLocalServer } from '@/cli/utilities/server/detect-local-server';
|
||||
import chalk from 'chalk';
|
||||
import type { Command } from 'commander';
|
||||
@@ -72,14 +73,19 @@ export const registerRemoteCommands = (program: Command): void => {
|
||||
.option('--api-key <apiKey>', 'API key for non-interactive auth')
|
||||
.option('--api-url <apiUrl>', 'Server URL')
|
||||
.option('--local', 'Connect to a local Twenty server (auto-detect)')
|
||||
.option('--test', 'Write to config.test.json (for integration tests)')
|
||||
.action(
|
||||
async (options: {
|
||||
as?: string;
|
||||
apiKey?: string;
|
||||
apiUrl?: string;
|
||||
local?: boolean;
|
||||
test?: boolean;
|
||||
}) => {
|
||||
const configService = new ConfigService();
|
||||
const configPath = options.test ? getConfigPath(true) : undefined;
|
||||
const configService = new ConfigService(
|
||||
configPath ? { configPath } : undefined,
|
||||
);
|
||||
const existingRemotes = await configService.getRemotes();
|
||||
|
||||
if (options.as !== undefined && existingRemotes.includes(options.as)) {
|
||||
|
||||
@@ -3,8 +3,10 @@ import {
|
||||
CONTAINER_NAME,
|
||||
containerExists,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TEST_PORT,
|
||||
getContainerPort,
|
||||
isContainerRunning,
|
||||
TEST_CONTAINER_NAME,
|
||||
} from '@/cli/utilities/server/docker-container';
|
||||
import { checkServerHealth } from '@/cli/utilities/server/detect-local-server';
|
||||
import chalk from 'chalk';
|
||||
@@ -19,9 +21,11 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('start')
|
||||
.description('Start a local Twenty server')
|
||||
.option('-p, --port <port>', 'HTTP port', String(DEFAULT_PORT))
|
||||
.action(async (options: { port: string }) => {
|
||||
const port = parseInt(options.port, 10);
|
||||
.option('-p, --port <port>', 'HTTP port')
|
||||
.option('--test', 'Start a separate test instance (port 2021)')
|
||||
.action(async (options: { port?: string; test?: boolean }) => {
|
||||
const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
const port = options.port ? parseInt(options.port, 10) : defaultPort;
|
||||
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
console.error(chalk.red('Invalid port number.'));
|
||||
@@ -30,6 +34,7 @@ export const registerServerCommands = (program: Command): void => {
|
||||
|
||||
const result = await serverStart({
|
||||
port,
|
||||
test: options.test,
|
||||
onProgress: (message) => console.log(chalk.gray(message)),
|
||||
});
|
||||
|
||||
@@ -42,14 +47,17 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('stop')
|
||||
.description('Stop the local Twenty server')
|
||||
.action(() => {
|
||||
if (!containerExists()) {
|
||||
.option('--test', 'Stop the test instance')
|
||||
.action((options: { test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
|
||||
if (!containerExists(containerName)) {
|
||||
console.log(chalk.yellow('No Twenty server container found.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
execSync(`docker stop ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
execSync(`docker stop ${containerName}`, { stdio: 'ignore' });
|
||||
console.log(chalk.green('Twenty server stopped.'));
|
||||
});
|
||||
|
||||
@@ -57,8 +65,11 @@ export const registerServerCommands = (program: Command): void => {
|
||||
.command('logs')
|
||||
.description('Stream Twenty server logs')
|
||||
.option('-n, --lines <lines>', 'Number of lines to show', '50')
|
||||
.action((options: { lines: string }) => {
|
||||
if (!containerExists()) {
|
||||
.option('--test', 'Show logs for the test instance')
|
||||
.action((options: { lines: string; test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
|
||||
if (!containerExists(containerName)) {
|
||||
console.log(chalk.yellow('No Twenty server container found.'));
|
||||
|
||||
return;
|
||||
@@ -67,7 +78,7 @@ export const registerServerCommands = (program: Command): void => {
|
||||
try {
|
||||
spawnSync(
|
||||
'docker',
|
||||
['logs', '-f', '--tail', options.lines, CONTAINER_NAME],
|
||||
['logs', '-f', '--tail', options.lines, containerName],
|
||||
{ stdio: 'inherit' },
|
||||
);
|
||||
} catch {
|
||||
@@ -78,18 +89,24 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('status')
|
||||
.description('Show Twenty server status')
|
||||
.action(async () => {
|
||||
if (!containerExists()) {
|
||||
.option('--test', 'Show status of the test instance')
|
||||
.action(async (options: { test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
|
||||
if (!containerExists(containerName)) {
|
||||
console.log(` Status: ${chalk.gray('not created')}`);
|
||||
console.log(
|
||||
chalk.gray(" Run 'yarn twenty server start' to create one."),
|
||||
chalk.gray(
|
||||
` Run 'yarn twenty server start${options.test ? ' --test' : ''}' to create one.`,
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const running = isContainerRunning();
|
||||
const port = running ? getContainerPort() : DEFAULT_PORT;
|
||||
const running = isContainerRunning(containerName);
|
||||
const port = running ? getContainerPort(containerName) : defaultPort;
|
||||
const healthy = running ? await checkServerHealth(port) : false;
|
||||
|
||||
const statusText = healthy
|
||||
@@ -109,25 +126,33 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('reset')
|
||||
.description('Delete all data and start fresh')
|
||||
.action(() => {
|
||||
if (containerExists()) {
|
||||
execSync(`docker rm -f ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
.option('--test', 'Reset the test instance')
|
||||
.action((options: { test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
const volumeData = options.test
|
||||
? 'twenty-app-dev-test-data'
|
||||
: 'twenty-app-dev-data';
|
||||
const volumeStorage = options.test
|
||||
? 'twenty-app-dev-test-storage'
|
||||
: 'twenty-app-dev-storage';
|
||||
|
||||
if (containerExists(containerName)) {
|
||||
execSync(`docker rm -f ${containerName}`, { stdio: 'ignore' });
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(
|
||||
'docker volume rm twenty-app-dev-data twenty-app-dev-storage',
|
||||
{
|
||||
stdio: 'ignore',
|
||||
},
|
||||
);
|
||||
execSync(`docker volume rm ${volumeData} ${volumeStorage}`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
} catch {
|
||||
// Volumes may not exist
|
||||
}
|
||||
|
||||
console.log(chalk.green('Twenty server data reset.'));
|
||||
console.log(
|
||||
chalk.gray("Run 'yarn twenty server start' to start a fresh instance."),
|
||||
chalk.gray(
|
||||
`Run 'yarn twenty server start${options.test ? ' --test' : ''}' to start a fresh instance.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { SERVER_ERROR_CODES, type CommandResult } from '@/cli/types';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
import { runSafe } from '@/cli/utilities/run-safe';
|
||||
import {
|
||||
checkDockerRunning,
|
||||
CONTAINER_NAME,
|
||||
containerExists,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TEST_PORT,
|
||||
getContainerPort,
|
||||
IMAGE,
|
||||
isContainerRunning,
|
||||
TEST_CONTAINER_NAME,
|
||||
} from '@/cli/utilities/server/docker-container';
|
||||
import {
|
||||
checkServerHealth,
|
||||
@@ -22,14 +25,17 @@ const HEALTH_TIMEOUT_MS = 180 * 1000;
|
||||
const MILESTONE_START = '==> START ';
|
||||
const MILESTONE_DONE = '==> DONE';
|
||||
|
||||
const waitForHealthy = async (port: number): Promise<boolean> => {
|
||||
const waitForHealthy = async (
|
||||
port: number,
|
||||
containerName: string,
|
||||
): Promise<boolean> => {
|
||||
const startTime = Date.now();
|
||||
const onProgress = (message: string) =>
|
||||
process.stdout.write(chalk.gray(message));
|
||||
|
||||
const logStream = spawn(
|
||||
'docker',
|
||||
['logs', '-f', '--since', '1s', CONTAINER_NAME],
|
||||
['logs', '-f', '--since', '1s', containerName],
|
||||
{ stdio: ['ignore', 'pipe', 'pipe'] },
|
||||
);
|
||||
|
||||
@@ -98,6 +104,7 @@ const waitForHealthy = async (port: number): Promise<boolean> => {
|
||||
|
||||
export type ServerStartOptions = {
|
||||
port?: number;
|
||||
test?: boolean;
|
||||
onProgress?: (message: string) => void;
|
||||
};
|
||||
|
||||
@@ -109,12 +116,23 @@ export type ServerStartResult = {
|
||||
const innerServerStart = async (
|
||||
options: ServerStartOptions = {},
|
||||
): Promise<CommandResult<ServerStartResult>> => {
|
||||
const { onProgress } = options;
|
||||
const { onProgress, test: isTest } = options;
|
||||
|
||||
const existingUrl = await detectLocalServer(options.port);
|
||||
const containerName = isTest ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
const defaultPort = isTest ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
const volumeData = isTest
|
||||
? 'twenty-app-dev-test-data'
|
||||
: 'twenty-app-dev-data';
|
||||
const volumeStorage = isTest
|
||||
? 'twenty-app-dev-test-storage'
|
||||
: 'twenty-app-dev-storage';
|
||||
|
||||
const existingUrl = await detectLocalServer(options.port ?? defaultPort);
|
||||
|
||||
if (existingUrl) {
|
||||
const configService = new ConfigService();
|
||||
const configService = new ConfigService(
|
||||
isTest ? { configPath: getConfigPath(true) } : undefined,
|
||||
);
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: existingUrl });
|
||||
@@ -139,12 +157,12 @@ const innerServerStart = async (
|
||||
};
|
||||
}
|
||||
|
||||
if (isContainerRunning()) {
|
||||
const port = getContainerPort();
|
||||
if (isContainerRunning(containerName)) {
|
||||
const port = getContainerPort(containerName);
|
||||
|
||||
onProgress?.('Container is running, waiting for it to become healthy...');
|
||||
|
||||
const healthy = await waitForHealthy(port);
|
||||
const healthy = await waitForHealthy(port, containerName);
|
||||
|
||||
if (!healthy) {
|
||||
return {
|
||||
@@ -159,7 +177,9 @@ const innerServerStart = async (
|
||||
}
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
const configService = new ConfigService(
|
||||
isTest ? { configPath: getConfigPath(true) } : undefined,
|
||||
);
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: url });
|
||||
@@ -169,21 +189,21 @@ const innerServerStart = async (
|
||||
return { success: true, data: { port, url } };
|
||||
}
|
||||
|
||||
let port = options.port ?? DEFAULT_PORT;
|
||||
let port = options.port ?? defaultPort;
|
||||
|
||||
if (containerExists()) {
|
||||
const existingPort = getContainerPort();
|
||||
if (containerExists(containerName)) {
|
||||
const existingPort = getContainerPort(containerName);
|
||||
|
||||
if (existingPort !== port) {
|
||||
onProgress?.(
|
||||
`Existing container uses port ${existingPort}. Run 'yarn twenty server reset' first to change ports.`,
|
||||
`Existing container uses port ${existingPort}. Run 'yarn twenty server reset${isTest ? ' --test' : ''}' first to change ports.`,
|
||||
);
|
||||
}
|
||||
|
||||
port = existingPort;
|
||||
|
||||
onProgress?.('Starting existing container...');
|
||||
execSync(`docker start ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
execSync(`docker start ${containerName}`, { stdio: 'ignore' });
|
||||
} else {
|
||||
onProgress?.('Starting Twenty container...');
|
||||
|
||||
@@ -193,7 +213,7 @@ const innerServerStart = async (
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
CONTAINER_NAME,
|
||||
containerName,
|
||||
'-p',
|
||||
`${port}:${port}`,
|
||||
'-e',
|
||||
@@ -201,9 +221,9 @@ const innerServerStart = async (
|
||||
'-e',
|
||||
`SERVER_URL=http://localhost:${port}`,
|
||||
'-v',
|
||||
'twenty-app-dev-data:/data/postgres',
|
||||
`${volumeData}:/data/postgres`,
|
||||
'-v',
|
||||
'twenty-app-dev-storage:/app/packages/twenty-server/.local-storage',
|
||||
`${volumeStorage}:/app/packages/twenty-server/.local-storage`,
|
||||
IMAGE,
|
||||
],
|
||||
{ stdio: 'inherit' },
|
||||
@@ -222,7 +242,7 @@ const innerServerStart = async (
|
||||
|
||||
onProgress?.('Waiting for Twenty to be ready...');
|
||||
|
||||
const healthy = await waitForHealthy(port);
|
||||
const healthy = await waitForHealthy(port, containerName);
|
||||
|
||||
if (!healthy) {
|
||||
return {
|
||||
@@ -237,7 +257,9 @@ const innerServerStart = async (
|
||||
}
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
const configService = new ConfigService(
|
||||
isTest ? { configPath: getConfigPath(true) } : undefined,
|
||||
);
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: url });
|
||||
|
||||
@@ -145,12 +145,6 @@ export class ApiClient {
|
||||
return this.tokenOverride;
|
||||
}
|
||||
|
||||
const envToken = process.env.TWENTY_TOKEN;
|
||||
|
||||
if (envToken) {
|
||||
return envToken;
|
||||
}
|
||||
|
||||
const config = await this.configService.getConfig();
|
||||
const accessToken = config.accessToken;
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ export class ConfigService {
|
||||
private readonly configPath: string;
|
||||
private static activeRemote = DEFAULT_REMOTE_NAME;
|
||||
|
||||
constructor() {
|
||||
this.configPath = getConfigPath();
|
||||
constructor(options?: { configPath?: string }) {
|
||||
this.configPath = options?.configPath ?? getConfigPath();
|
||||
}
|
||||
|
||||
static setActiveRemote(name?: string) {
|
||||
@@ -127,13 +127,6 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
async getConfig(): Promise<RemoteConfig> {
|
||||
if (process.env.TWENTY_TOKEN && process.env.TWENTY_API_URL) {
|
||||
return {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
accessToken: process.env.TWENTY_TOKEN,
|
||||
};
|
||||
}
|
||||
|
||||
return this.getConfigForRemote(this.getActiveRemoteName());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
const TWENTY_DIR = path.join(os.homedir(), '.twenty');
|
||||
|
||||
export const getConfigPath = (): string => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return path.join(TEST_CONFIG_DIR, 'config.json');
|
||||
export const getConfigPath = (test = false): string => {
|
||||
if (test || process.env.NODE_ENV === 'test') {
|
||||
return path.join(TWENTY_DIR, 'config.test.json');
|
||||
}
|
||||
|
||||
return path.join(os.homedir(), '.twenty', 'config.json');
|
||||
return path.join(TWENTY_DIR, 'config.json');
|
||||
};
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
export const CONTAINER_NAME = 'twenty-app-dev';
|
||||
export const TEST_CONTAINER_NAME = 'twenty-app-dev-test';
|
||||
export const IMAGE = 'twentycrm/twenty-app-dev:latest';
|
||||
export const DEFAULT_PORT = 2020;
|
||||
export const DEFAULT_TEST_PORT = 2021;
|
||||
|
||||
export const isContainerRunning = (): boolean => {
|
||||
export const isContainerRunning = (containerName = CONTAINER_NAME): boolean => {
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{.State.Running}}' ${CONTAINER_NAME}`,
|
||||
`docker inspect -f '{{.State.Running}}' ${containerName}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
).trim();
|
||||
|
||||
@@ -17,24 +19,27 @@ export const isContainerRunning = (): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getContainerPort = (): number => {
|
||||
export const getContainerPort = (containerName = CONTAINER_NAME): number => {
|
||||
const defaultPort =
|
||||
containerName === TEST_CONTAINER_NAME ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' ${CONTAINER_NAME}`,
|
||||
`docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' ${containerName}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
);
|
||||
|
||||
const match = result.match(/^NODE_PORT=(\d+)$/m);
|
||||
|
||||
return match ? parseInt(match[1], 10) : DEFAULT_PORT;
|
||||
return match ? parseInt(match[1], 10) : defaultPort;
|
||||
} catch {
|
||||
return DEFAULT_PORT;
|
||||
return defaultPort;
|
||||
}
|
||||
};
|
||||
|
||||
export const containerExists = (): boolean => {
|
||||
export const containerExists = (containerName = CONTAINER_NAME): boolean => {
|
||||
try {
|
||||
execSync(`docker inspect ${CONTAINER_NAME}`, {
|
||||
execSync(`docker inspect ${containerName}`, {
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
});
|
||||
|
||||
|
||||
@@ -25,9 +25,10 @@ export default defineConfig({
|
||||
concurrent: false,
|
||||
},
|
||||
env: {
|
||||
TWENTY_API_URL: 'http://localhost:3000',
|
||||
TWENTY_API_URL: process.env.TWENTY_API_URL ?? 'http://localhost:2021',
|
||||
TWENTY_API_KEY:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik',
|
||||
process.env.TWENTY_API_KEY ??
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ1c2VySWQiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjQ5MDQ4ODE3MDR9.9S4wc0MOr5iczsomlFxZdOHD1IRDS4dnRSwNVNpctF4',
|
||||
},
|
||||
setupFiles: ['src/cli/__tests__/constants/setupTest.ts'],
|
||||
globalSetup: undefined,
|
||||
|
||||
@@ -18,14 +18,6 @@ export default defineConfig({
|
||||
truncateThreshold: 0,
|
||||
},
|
||||
fileParallelism: false,
|
||||
env: {
|
||||
TWENTY_API_URL: 'http://localhost:3000',
|
||||
TWENTY_API_KEY:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik',
|
||||
},
|
||||
setupFiles: [
|
||||
'src/cli/__tests__/constants/setupTest.ts',
|
||||
'src/cli/__tests__/integration/utils/setup-app-dev-mocks.ts',
|
||||
],
|
||||
setupFiles: ['src/cli/__tests__/integration/utils/setup-app-dev-mocks.ts'],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { MarketplaceCatalogSyncCronJob } from 'src/engine/core-modules/application/application-marketplace/crons/marketplace-catalog-sync.cron.job';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
@@ -21,7 +20,7 @@ import { MessageQueueService } from 'src/engine/core-modules/message-queue/servi
|
||||
|
||||
@MetadataResolver()
|
||||
@UseFilters(ApplicationRegistrationExceptionFilter)
|
||||
@UseGuards(UserAuthGuard, WorkspaceAuthGuard, NoPermissionGuard)
|
||||
@UseGuards(WorkspaceAuthGuard, NoPermissionGuard)
|
||||
export class MarketplaceResolver {
|
||||
constructor(
|
||||
private readonly marketplaceQueryService: MarketplaceQueryService,
|
||||
|
||||
Reference in New Issue
Block a user