Vai al contenuto principale
L’SDK fornisce API programmatiche che ti consentono di compilare, distribuire, installare e disinstallare la tua app dal codice di test. In combinazione con Vitest e i client API tipizzati, puoi scrivere test di integrazione che verificano che la tua app funzioni end-to-end contro un server Twenty reale.

Uso dei pacchetti npm

Puoi installare e usare qualsiasi pacchetto npm nella tua app. Sia le funzioni logiche sia i componenti front-end vengono impacchettati con esbuild, che incorpora tutte le dipendenze nell’output — non sono necessari i node_modules a runtime.

Installazione di un pacchetto

yarn add axios
Quindi importalo nel tuo codice:
src/logic-functions/fetch-data.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import axios from 'axios';

const handler = async (): Promise<any> => {
  const { data } = await axios.get('https://api.example.com/data');

  return { data };
};

export default defineLogicFunction({
  universalIdentifier: '...',
  name: 'fetch-data',
  description: 'Fetches data from an external API',
  timeoutSeconds: 10,
  handler,
});
Lo stesso vale per i componenti front-end:
src/front-components/chart.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { format } from 'date-fns';

const DateWidget = () => {
  return <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
};

export default defineFrontComponent({
  universalIdentifier: '...',
  name: 'date-widget',
  component: DateWidget,
});

Come funziona il bundling

La fase di build usa esbuild per produrre un singolo file autonomo per ogni funzione logica e per ogni componente front-end. Tutti i pacchetti importati sono incorporati nel bundle. Le funzioni logiche vengono eseguite in un ambiente Node.js. I moduli integrati di Node (fs, path, crypto, http, ecc.) sono disponibili e non necessitano di essere installati. I componenti front-end vengono eseguiti in un Web Worker. I moduli integrati di Node non sono disponibili — solo le API del browser e i pacchetti npm che funzionano in un ambiente browser. Entrambi gli ambienti hanno twenty-client-sdk/core e twenty-client-sdk/metadata disponibili come moduli preforniti — questi non vengono inclusi nel bundle ma vengono risolti a runtime dal server.

Impostazione

L’app generata tramite scaffolding include già Vitest. Se la configuri manualmente, installa le dipendenze:
yarn add -D vitest vite-tsconfig-paths
Crea un vitest.config.ts alla radice della tua app:
vitest.config.ts
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  plugins: [
    tsconfigPaths({
      projects: ['tsconfig.spec.json'],
      ignoreConfigErrors: true,
    }),
  ],
  test: {
    testTimeout: 120_000,
    hookTimeout: 120_000,
    include: ['src/**/*.integration-test.ts'],
    setupFiles: ['src/__tests__/setup-test.ts'],
    env: {
      TWENTY_API_URL: 'http://localhost:2020',
      TWENTY_API_KEY: 'your-api-key',
    },
  },
});
Crea un file di setup che verifichi che il server sia raggiungibile prima dell’esecuzione dei test:
src/__tests__/setup-test.ts
import * as fs from 'fs';
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');

beforeAll(async () => {
  // Verify the server is running
  const response = await fetch(`${TWENTY_API_URL}/healthz`);

  if (!response.ok) {
    throw new Error(
      `Twenty server is not reachable at ${TWENTY_API_URL}. ` +
        'Start the server before running integration tests.',
    );
  }

  // Write a temporary config for the SDK
  fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });

  fs.writeFileSync(
    path.join(TEST_CONFIG_DIR, 'config.json'),
    JSON.stringify({
      remotes: {
        local: {
          apiUrl: process.env.TWENTY_API_URL,
          apiKey: process.env.TWENTY_API_KEY,
        },
      },
      defaultRemote: 'local',
    }, null, 2),
  );
});

API programmatiche dell’SDK

Il sottopercorso twenty-sdk/cli esporta funzioni che puoi chiamare direttamente dal codice di test:
FunzioneDescrizione
appBuildCompila l’app e, opzionalmente, crea un tarball
appDeployCarica un tarball sul server
appInstallInstalla l’app nello spazio di lavoro attivo
appUninstallDisinstalla l’app dallo spazio di lavoro attivo
Ogni funzione restituisce un oggetto risultato con success: boolean e data oppure error.

Scrivere un test di integrazione

Ecco un esempio completo che compila, distribuisce e installa l’app, quindi verifica che compaia nello spazio di lavoro:
src/__tests__/app-install.integration-test.ts
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const APP_PATH = process.cwd();

describe('App installation', () => {
  beforeAll(async () => {
    const buildResult = await appBuild({
      appPath: APP_PATH,
      tarball: true,
      onProgress: (message: string) => console.log(`[build] ${message}`),
    });

    if (!buildResult.success) {
      throw new Error(`Build failed: ${buildResult.error?.message}`);
    }

    const deployResult = await appDeploy({
      tarballPath: buildResult.data.tarballPath!,
      onProgress: (message: string) => console.log(`[deploy] ${message}`),
    });

    if (!deployResult.success) {
      throw new Error(`Deploy failed: ${deployResult.error?.message}`);
    }

    const installResult = await appInstall({ appPath: APP_PATH });

    if (!installResult.success) {
      throw new Error(`Install failed: ${installResult.error?.message}`);
    }
  });

  afterAll(async () => {
    await appUninstall({ appPath: APP_PATH });
  });

  it('should find the installed app in the workspace', async () => {
    const metadataClient = new MetadataApiClient();

    const result = await metadataClient.query({
      findManyApplications: {
        id: true,
        name: true,
        universalIdentifier: true,
      },
    });

    const installedApp = result.findManyApplications.find(
      (app: { universalIdentifier: string }) =>
        app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
    );

    expect(installedApp).toBeDefined();
  });
});

Esecuzione dei test

Assicurati che il tuo server Twenty locale sia in esecuzione, quindi:
yarn test
Oppure in modalità watch durante lo sviluppo:
yarn test:watch

Controllo dei tipi

Puoi anche eseguire il controllo dei tipi sulla tua app senza eseguire i test:
yarn twenty dev:typecheck
Questo esegue tsc --noEmit e riporta eventuali errori di tipo.

CI con GitHub Actions

Lo strumento di scaffolding genera un workflow GitHub Actions pronto all’uso in .github/workflows/ci.yml. Esegue automaticamente i test di integrazione a ogni push su main e sulle pull request. Il workflow:
  1. Esegue il checkout del tuo codice
  2. Avvia un server Twenty temporaneo utilizzando l’azione twentyhq/twenty/.github/actions/spawn-twenty-docker-image
  3. Installa le dipendenze con yarn install --immutable
  4. Esegue yarn test con TWENTY_API_URL e TWENTY_API_KEY iniettati dagli output dell’azione
.github/workflows/ci.yml
name: CI

on:
  push:
    branches:
      - main
  pull_request: {}

env:
  TWENTY_VERSION: latest

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Spawn Twenty instance
        id: twenty
        uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
        with:
          twenty-version: ${{ env.TWENTY_VERSION }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Enable Corepack
        run: corepack enable

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --immutable

      - name: Run integration tests
        run: yarn test
        env:
          TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
          TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
Non è necessario configurare alcun secret — l’azione spawn-twenty-docker-image avvia un server Twenty effimero direttamente nel runner e fornisce i dettagli di connessione. Il secret GITHUB_TOKEN è fornito automaticamente da GitHub. Per fissare una versione specifica di Twenty invece di latest, modifica la variabile d’ambiente TWENTY_VERSION all’inizio del workflow.