Saltar al contenido principal
El SDK proporciona APIs programáticas que te permiten compilar, desplegar, instalar y desinstalar tu aplicación desde código de pruebas. Combinado con Vitest y los clientes de API tipados, puedes escribir pruebas de integración que verifiquen que tu aplicación funciona de extremo a extremo contra un servidor real de Twenty.

Uso de paquetes de npm

Puedes instalar y usar cualquier paquete de npm en tu aplicación. Tanto las funciones de lógica como los componentes de frontend se empaquetan con esbuild, que incorpora todas las dependencias en la salida — no se necesitan node_modules en tiempo de ejecución.

Instalar un paquete

yarn add axios
Luego impórtalo en tu código:
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 mismo funciona para los componentes de frontend:
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,
});

Cómo funciona el empaquetado

El paso de compilación usa esbuild para producir un solo archivo autónomo por función de lógica y por componente de frontend. Todos los paquetes importados se insertan en el bundle. Las funciones de lógica se ejecutan en un entorno Node.js. Los módulos integrados de Node (fs, path, crypto, http, etc.) están disponibles y no necesitan instalarse. Los componentes de frontend se ejecutan en un Web Worker. Los módulos integrados de Node no están disponibles — solo las APIs del navegador y paquetes de npm que funcionen en un entorno de navegador. Ambos entornos tienen twenty-client-sdk/core y twenty-client-sdk/metadata disponibles como módulos preproporcionados — estos no se incluyen en el bundle sino que se resuelven en tiempo de ejecución por el servidor.

Configuración

La aplicación generada ya incluye Vitest. Si lo configuras manualmente, instala las dependencias:
yarn add -D vitest vite-tsconfig-paths
Crea un vitest.config.ts en la raíz de tu aplicación:
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 archivo de configuración que verifique que el servidor es accesible antes de ejecutar las pruebas:
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),
  );
});

APIs programáticas del SDK

La subruta twenty-sdk/cli exporta funciones que puedes invocar directamente desde el código de pruebas:
FunciónDescripción
appBuildCompilar la aplicación y opcionalmente empaquetar un tarball
appDeploySubir un tarball al servidor
appInstallInstalar la aplicación en el espacio de trabajo activo
appUninstallDesinstalar la aplicación del espacio de trabajo activo
Cada función devuelve un objeto de resultado con success: boolean y data o error.

Escribir una prueba de integración

Aquí tienes un ejemplo completo que compila, despliega e instala la aplicación, y luego verifica que aparezca en el espacio de trabajo:
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();
  });
});

Ejecutar pruebas

Asegúrate de que tu servidor local de Twenty esté en ejecución y luego:
yarn test
O en modo watch durante el desarrollo:
yarn test:watch

Comprobación de tipos

También puedes ejecutar la comprobación de tipos en tu aplicación sin ejecutar pruebas:
yarn twenty dev:typecheck
Esto ejecuta tsc --noEmit e informa cualquier error de tipo.

CI con GitHub Actions

El generador crea un flujo de trabajo de GitHub Actions listo para usar en .github/workflows/ci.yml. Ejecuta tus pruebas de integración automáticamente en cada push a main y en los pull requests. El flujo de trabajo:
  1. Obtiene tu código
  2. Inicia un servidor temporal de Twenty usando la acción twentyhq/twenty/.github/actions/spawn-twenty-docker-image
  3. Instala las dependencias con yarn install --immutable
  4. Ejecuta yarn test con TWENTY_API_URL y TWENTY_API_KEY inyectados a partir de las salidas de la acción
.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 }}
No necesitas configurar secretos: la acción spawn-twenty-docker-image inicia un servidor efímero de Twenty directamente en el runner y devuelve los detalles de conexión. El secreto GITHUB_TOKEN lo proporciona GitHub automáticamente. Para fijar una versión específica de Twenty en lugar de latest, cambia la variable de entorno TWENTY_VERSION al inicio del flujo de trabajo.