From 15eb3e7edccdf4e9770a00a07bfbd026420f7c3b Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 8 Apr 2026 06:49:10 +0200 Subject: [PATCH] feat(sdk): use config file as single source of truth, remove env var fallbacks (#19409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- .../spawn-twenty-app-dev-test/action.yml | 47 ++++++++++++ .../ci-create-app-e2e-hello-world.yaml | 46 ++---------- .../workflows/ci-create-app-e2e-minimal.yaml | 46 ++---------- .../workflows/ci-create-app-e2e-postcard.yaml | 46 ++---------- .../workflows/ci-example-app-hello-world.yaml | 64 ++++++++++++++++ .../workflows/ci-example-app-postcard.yaml | 64 ++++++++++++++++ .github/workflows/ci-sdk.yaml | 2 + .../{.github => github}/workflows/ci.yml | 16 ++-- .../template/src/__tests__/setup-test.ts | 54 ++++++++------ .../src/constants/template/vitest.config.ts | 5 +- .../src/utils/app-template.ts | 19 +++-- .../hello-world/.github/workflows/ci.yml | 16 ++-- .../hello-world/src/__tests__/setup-test.ts | 54 ++++++++------ .../examples/hello-world/vitest.config.ts | 6 +- .../postcard/.github/workflows/ci.yml | 16 ++-- .../postcard/src/__tests__/setup-test.ts | 54 ++++++++------ .../examples/postcard/vitest.config.ts | 5 +- .../apps/rich-app/__e2e__/health.e2e-spec.ts | 12 ++- .../constants/server-url.constant.ts | 8 +- .../src/cli/__tests__/constants/setupTest.ts | 44 ++++++++--- packages/twenty-sdk/src/cli/commands/dev.ts | 5 +- .../twenty-sdk/src/cli/commands/remote.ts | 8 +- .../twenty-sdk/src/cli/commands/server.ts | 73 +++++++++++++------ .../src/cli/operations/server-start.ts | 60 ++++++++++----- .../src/cli/utilities/api/api-client.ts | 6 -- .../cli/utilities/config/config-service.ts | 11 +-- .../cli/utilities/config/get-config-path.ts | 10 +-- .../cli/utilities/server/docker-container.ts | 21 ++++-- packages/twenty-sdk/vitest.e2e.config.ts | 5 +- .../twenty-sdk/vitest.integration.config.ts | 10 +-- .../marketplace.resolver.ts | 3 +- 31 files changed, 523 insertions(+), 313 deletions(-) create mode 100644 .github/actions/spawn-twenty-app-dev-test/action.yml create mode 100644 .github/workflows/ci-example-app-hello-world.yaml create mode 100644 .github/workflows/ci-example-app-postcard.yaml rename packages/create-twenty-app/src/constants/template/{.github => github}/workflows/ci.yml (66%) diff --git a/.github/actions/spawn-twenty-app-dev-test/action.yml b/.github/actions/spawn-twenty-app-dev-test/action.yml new file mode 100644 index 00000000000..bfc9767777b --- /dev/null +++ b/.github/actions/spawn-twenty-app-dev-test/action.yml @@ -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)" diff --git a/.github/workflows/ci-create-app-e2e-hello-world.yaml b/.github/workflows/ci-create-app-e2e-hello-world.yaml index baacff89023..7d6e13e134f 100644 --- a/.github/workflows/ci-create-app-e2e-hello-world.yaml +++ b/.github/workflows/ci-create-app-e2e-hello-world.yaml @@ -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 diff --git a/.github/workflows/ci-create-app-e2e-minimal.yaml b/.github/workflows/ci-create-app-e2e-minimal.yaml index cf03b338f24..9d728591515 100644 --- a/.github/workflows/ci-create-app-e2e-minimal.yaml +++ b/.github/workflows/ci-create-app-e2e-minimal.yaml @@ -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 diff --git a/.github/workflows/ci-create-app-e2e-postcard.yaml b/.github/workflows/ci-create-app-e2e-postcard.yaml index 707faa1313e..b38812d4834 100644 --- a/.github/workflows/ci-create-app-e2e-postcard.yaml +++ b/.github/workflows/ci-create-app-e2e-postcard.yaml @@ -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 diff --git a/.github/workflows/ci-example-app-hello-world.yaml b/.github/workflows/ci-example-app-hello-world.yaml new file mode 100644 index 00000000000..f53015fcf7f --- /dev/null +++ b/.github/workflows/ci-example-app-hello-world.yaml @@ -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 diff --git a/.github/workflows/ci-example-app-postcard.yaml b/.github/workflows/ci-example-app-postcard.yaml new file mode 100644 index 00000000000..a4dfe3309ab --- /dev/null +++ b/.github/workflows/ci-example-app-postcard.yaml @@ -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 diff --git a/.github/workflows/ci-sdk.yaml b/.github/workflows/ci-sdk.yaml index 73a353ef1fa..f169f0c9a10 100644 --- a/.github/workflows/ci-sdk.yaml +++ b/.github/workflows/ci-sdk.yaml @@ -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 diff --git a/packages/create-twenty-app/src/constants/template/.github/workflows/ci.yml b/packages/create-twenty-app/src/constants/template/github/workflows/ci.yml similarity index 66% rename from packages/create-twenty-app/src/constants/template/.github/workflows/ci.yml rename to packages/create-twenty-app/src/constants/template/github/workflows/ci.yml index 92bd0e994f9..b98f6a64e55 100644 --- a/packages/create-twenty-app/src/constants/template/.github/workflows/ci.yml +++ b/packages/create-twenty-app/src/constants/template/github/workflows/ci.yml @@ -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 }} diff --git a/packages/create-twenty-app/src/constants/template/src/__tests__/setup-test.ts b/packages/create-twenty-app/src/constants/template/src/__tests__/setup-test.ts index 16e00da4063..40cb3888dbb 100644 --- a/packages/create-twenty-app/src/constants/template/src/__tests__/setup-test.ts +++ b/packages/create-twenty-app/src/constants/template/src/__tests__/setup-test.ts @@ -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; }); diff --git a/packages/create-twenty-app/src/constants/template/vitest.config.ts b/packages/create-twenty-app/src/constants/template/vitest.config.ts index 2fed127a84c..9c698b83407 100644 --- a/packages/create-twenty-app/src/constants/template/vitest.config.ts +++ b/packages/create-twenty-app/src/constants/template/vitest.config.ts @@ -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', }, }, }); diff --git a/packages/create-twenty-app/src/utils/app-template.ts b/packages/create-twenty-app/src/utils/app-template.ts index c635b0a7e07..4d2c043149c 100644 --- a/packages/create-twenty-app/src/utils/app-template.ts +++ b/packages/create-twenty-app/src/utils/app-template.ts @@ -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)); + } } }; diff --git a/packages/twenty-apps/examples/hello-world/.github/workflows/ci.yml b/packages/twenty-apps/examples/hello-world/.github/workflows/ci.yml index 92bd0e994f9..b98f6a64e55 100644 --- a/packages/twenty-apps/examples/hello-world/.github/workflows/ci.yml +++ b/packages/twenty-apps/examples/hello-world/.github/workflows/ci.yml @@ -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 }} diff --git a/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts b/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts index c3bb17f8820..40cb3888dbb 100644 --- a/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts +++ b/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts @@ -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; }); diff --git a/packages/twenty-apps/examples/hello-world/vitest.config.ts b/packages/twenty-apps/examples/hello-world/vitest.config.ts index b904487c9d7..9c698b83407 100644 --- a/packages/twenty-apps/examples/hello-world/vitest.config.ts +++ b/packages/twenty-apps/examples/hello-world/vitest.config.ts @@ -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', }, }, }); diff --git a/packages/twenty-apps/examples/postcard/.github/workflows/ci.yml b/packages/twenty-apps/examples/postcard/.github/workflows/ci.yml index 92bd0e994f9..b98f6a64e55 100644 --- a/packages/twenty-apps/examples/postcard/.github/workflows/ci.yml +++ b/packages/twenty-apps/examples/postcard/.github/workflows/ci.yml @@ -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 }} diff --git a/packages/twenty-apps/examples/postcard/src/__tests__/setup-test.ts b/packages/twenty-apps/examples/postcard/src/__tests__/setup-test.ts index 16e00da4063..40cb3888dbb 100644 --- a/packages/twenty-apps/examples/postcard/src/__tests__/setup-test.ts +++ b/packages/twenty-apps/examples/postcard/src/__tests__/setup-test.ts @@ -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; }); diff --git a/packages/twenty-apps/examples/postcard/vitest.config.ts b/packages/twenty-apps/examples/postcard/vitest.config.ts index 2fed127a84c..9c698b83407 100644 --- a/packages/twenty-apps/examples/postcard/vitest.config.ts +++ b/packages/twenty-apps/examples/postcard/vitest.config.ts @@ -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', }, }, }); diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__e2e__/health.e2e-spec.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__e2e__/health.e2e-spec.ts index 57807d5dc60..af218afe8fc 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__e2e__/health.e2e-spec.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__e2e__/health.e2e-spec.ts @@ -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(); }); diff --git a/packages/twenty-sdk/src/cli/__tests__/constants/server-url.constant.ts b/packages/twenty-sdk/src/cli/__tests__/constants/server-url.constant.ts index d9fa7c5add3..c06ee30cd08 100644 --- a/packages/twenty-sdk/src/cli/__tests__/constants/server-url.constant.ts +++ b/packages/twenty-sdk/src/cli/__tests__/constants/server-url.constant.ts @@ -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 => { + const config = await new ConfigService().getConfig(); + + return config.apiUrl; +}; diff --git a/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts b/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts index 82331a32e19..9d2241f0465 100644 --- a/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts @@ -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; }); diff --git a/packages/twenty-sdk/src/cli/commands/dev.ts b/packages/twenty-sdk/src/cli/commands/dev.ts index e23f3a450b8..3c7a29ce512 100644 --- a/packages/twenty-sdk/src/cli/commands/dev.ts +++ b/packages/twenty-sdk/src/cli/commands/dev.ts @@ -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) { diff --git a/packages/twenty-sdk/src/cli/commands/remote.ts b/packages/twenty-sdk/src/cli/commands/remote.ts index 2773f74ab7a..7fa4717c337 100644 --- a/packages/twenty-sdk/src/cli/commands/remote.ts +++ b/packages/twenty-sdk/src/cli/commands/remote.ts @@ -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 ', 'API key for non-interactive auth') .option('--api-url ', '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)) { diff --git a/packages/twenty-sdk/src/cli/commands/server.ts b/packages/twenty-sdk/src/cli/commands/server.ts index c95424a0789..659fb5c12d7 100644 --- a/packages/twenty-sdk/src/cli/commands/server.ts +++ b/packages/twenty-sdk/src/cli/commands/server.ts @@ -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 ', 'HTTP port', String(DEFAULT_PORT)) - .action(async (options: { port: string }) => { - const port = parseInt(options.port, 10); + .option('-p, --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 ', '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.`, + ), ); }); }; diff --git a/packages/twenty-sdk/src/cli/operations/server-start.ts b/packages/twenty-sdk/src/cli/operations/server-start.ts index b7b84b09851..b0c8d505a0e 100644 --- a/packages/twenty-sdk/src/cli/operations/server-start.ts +++ b/packages/twenty-sdk/src/cli/operations/server-start.ts @@ -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 => { +const waitForHealthy = async ( + port: number, + containerName: string, +): Promise => { 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 => { export type ServerStartOptions = { port?: number; + test?: boolean; onProgress?: (message: string) => void; }; @@ -109,12 +116,23 @@ export type ServerStartResult = { const innerServerStart = async ( options: ServerStartOptions = {}, ): Promise> => { - 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 }); diff --git a/packages/twenty-sdk/src/cli/utilities/api/api-client.ts b/packages/twenty-sdk/src/cli/utilities/api/api-client.ts index 77d3581f622..f88568bee37 100644 --- a/packages/twenty-sdk/src/cli/utilities/api/api-client.ts +++ b/packages/twenty-sdk/src/cli/utilities/api/api-client.ts @@ -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; diff --git a/packages/twenty-sdk/src/cli/utilities/config/config-service.ts b/packages/twenty-sdk/src/cli/utilities/config/config-service.ts index 0e49d21f1be..41a175318ee 100644 --- a/packages/twenty-sdk/src/cli/utilities/config/config-service.ts +++ b/packages/twenty-sdk/src/cli/utilities/config/config-service.ts @@ -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 { - 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()); } diff --git a/packages/twenty-sdk/src/cli/utilities/config/get-config-path.ts b/packages/twenty-sdk/src/cli/utilities/config/get-config-path.ts index e0ff8143dfa..8a9a340ee8a 100644 --- a/packages/twenty-sdk/src/cli/utilities/config/get-config-path.ts +++ b/packages/twenty-sdk/src/cli/utilities/config/get-config-path.ts @@ -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'); }; diff --git a/packages/twenty-sdk/src/cli/utilities/server/docker-container.ts b/packages/twenty-sdk/src/cli/utilities/server/docker-container.ts index 8af3c39d781..7285a8e5423 100644 --- a/packages/twenty-sdk/src/cli/utilities/server/docker-container.ts +++ b/packages/twenty-sdk/src/cli/utilities/server/docker-container.ts @@ -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'], }); diff --git a/packages/twenty-sdk/vitest.e2e.config.ts b/packages/twenty-sdk/vitest.e2e.config.ts index 393312c1e4e..a60b30184df 100644 --- a/packages/twenty-sdk/vitest.e2e.config.ts +++ b/packages/twenty-sdk/vitest.e2e.config.ts @@ -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, diff --git a/packages/twenty-sdk/vitest.integration.config.ts b/packages/twenty-sdk/vitest.integration.config.ts index ec581a05972..ecde4c6d221 100644 --- a/packages/twenty-sdk/vitest.integration.config.ts +++ b/packages/twenty-sdk/vitest.integration.config.ts @@ -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'], }, }); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.resolver.ts index 9d4f1765e55..75e94657041 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.resolver.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,