Vai al contenuto principale

Asset pubblici (cartella public/)

La cartella public/ alla radice della tua app contiene file statici — immagini, icone, font o qualsiasi altro asset di cui la tua app ha bisogno a runtime. Questi file sono inclusi automaticamente nelle build, sincronizzati durante la modalità di sviluppo e caricati sul server. I file posizionati in public/ sono:
  • Pubblicamente accessibili — una volta sincronizzati sul server, gli asset sono serviti a un URL pubblico. Non è necessaria alcuna autenticazione per accedervi.
  • Disponibili nei componenti front-end — usa gli URL degli asset per visualizzare immagini, icone o qualsiasi media all’interno dei tuoi componenti React.
  • Disponibili nelle funzioni logiche — fai riferimento agli URL degli asset nelle email, nelle risposte API o in qualsiasi logica lato server.
  • Usati per i metadati del marketplace — i campi logoUrl e screenshots in defineApplication() fanno riferimento a file di questa cartella (ad es., public/logo.png). Questi vengono visualizzati nel marketplace quando la tua app viene pubblicata.
  • Sincronizzati automaticamente in modalità dev — quando aggiungi, aggiorni o elimini un file in public/, viene sincronizzato automaticamente con il server. Nessun riavvio necessario.
  • Inclusi nelle buildyarn twenty build raggruppa tutti gli asset pubblici nell’output di distribuzione.

Accedere agli asset pubblici con getPublicAssetUrl

Usa l’helper getPublicAssetUrl da twenty-sdk per ottenere l’URL completo di un file nella tua directory public/. Funziona sia nelle funzioni logiche che nei componenti front-end. In una funzione logica:
src/logic-functions/send-invoice.ts
import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define';

const handler = async (): Promise<any> => {
  const logoUrl = getPublicAssetUrl('logo.png');
  const invoiceUrl = getPublicAssetUrl('templates/invoice.png');

  // Fetch the file content (no auth required — public endpoint)
  const response = await fetch(invoiceUrl);
  const buffer = await response.arrayBuffer();

  return { logoUrl, size: buffer.byteLength };
};

export default defineLogicFunction({
  universalIdentifier: 'a1b2c3d4-...',
  name: 'send-invoice',
  description: 'Sends an invoice with the app logo',
  timeoutSeconds: 10,
  handler,
});
In un componente front-end:
src/front-components/company-card.tsx
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';

export default defineFrontComponent(() => {
  const logoUrl = getPublicAssetUrl('logo.png');

  return <img src={logoUrl} alt="App logo" />;
});
L’argomento path è relativo alla cartella public/ della tua app. Sia getPublicAssetUrl('logo.png') sia getPublicAssetUrl('public/logo.png') risolvono allo stesso URL — il prefisso public/ viene rimosso automaticamente se presente.

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.

Testare la tua app

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.

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 typecheck
Questo esegue tsc --noEmit e riporta eventuali errori di tipo.

Riferimento CLI

Oltre a dev, build, add e typecheck, la CLI fornisce comandi per eseguire funzioni, visualizzare i log e gestire le installazioni delle app.

Esecuzione delle funzioni (yarn twenty exec)

Esegui manualmente una funzione logica senza attivarla tramite HTTP, cron o evento del database:
# Execute by function name
yarn twenty exec -n create-new-post-card

# Execute by universalIdentifier
yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf

# Pass a JSON payload
yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}'

# Execute the post-install function
yarn twenty exec --postInstall

Visualizzazione dei log delle funzioni (yarn twenty logs)

Esegui lo streaming dei log di esecuzione per le funzioni logiche della tua app:
# Stream all function logs
yarn twenty logs

# Filter by function name
yarn twenty logs -n create-new-post-card

# Filter by universalIdentifier
yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
Questo è diverso da yarn twenty server logs, che mostra i log del container Docker. yarn twenty logs mostra i log di esecuzione delle funzioni della tua app dal server Twenty.

Disinstallazione di un’app (yarn twenty uninstall)

Rimuovi la tua app dallo spazio di lavoro attivo:
yarn twenty uninstall

# Skip the confirmation prompt
yarn twenty uninstall --yes

Gestione dei remoti

Un remoto è un server Twenty a cui la tua app si connette. Durante la configurazione, lo strumento di scaffolding ne crea uno automaticamente per te. Puoi aggiungere altri remoti o passare da uno all’altro in qualsiasi momento.
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add

# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local

# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote

# List all configured remotes
yarn twenty remote list

# Switch the active remote
yarn twenty remote switch <name>
Le tue credenziali sono archiviate in ~/.twenty/config.json.

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.