diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/project-structure.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/project-structure.mdx index c4587a3a3e..671be042a7 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/project-structure.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/project-structure.mdx @@ -14,15 +14,26 @@ my-twenty-app/ default-role.ts # Permissions for logic functions constants/ universal-identifiers.ts # Auto-generated UUIDs and metadata + front-components/ + main-page.tsx # Welcome page component + navigation-menu-items/ + main-page.navigation-menu-item.ts # Sidebar entry for the welcome page + page-layouts/ + main-page.page-layout.ts # Standalone page hosting the component __tests__/ - setup-test.ts - app-install.integration-test.ts - .github/workflows/ci.yml # GitHub Actions - public/ # Static assets - vitest.config.ts # Test runner config + application-config.test.ts # Unit test + global-setup.ts # Integration test setup (sync + uninstall) + schema.integration-test.ts # Integration test against a live server + .github/workflows/ + ci.yml # Lint, typecheck, unit + integration tests + cd.yml # Deploy + install on push to main + public/ + logo.svg # Static assets + vitest.config.ts # Integration test runner config + vitest.unit.config.ts # Unit test runner config tsconfig.json, tsconfig.spec.json .nvmrc, .yarnrc.yml, .oxlintrc.json - README.md, LLMS.md + README.md, AGENTS.md, CLAUDE.md ``` ## Key files @@ -32,8 +43,10 @@ my-twenty-app/ | `src/application-config.ts` | **Required.** The main configuration file for your app. | | `src/default-role.ts` | Default role controlling what your logic functions can access. | | `src/constants/universal-identifiers.ts` | Auto-generated UUIDs and metadata (display name, description). | -| `src/__tests__/` | Integration tests (setup + example test). | +| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | A starter welcome page: a front component rendered by a standalone page layout, reachable from the sidebar. | +| `src/__tests__/` | A unit test plus an integration test (with its global setup) that syncs the app against a real server. | | `public/` | Static assets (images, fonts) served with your app. | +| `AGENTS.md` / `CLAUDE.md` | Guidance for AI coding agents working on the app. | **File organization is up to you.** The folders above are conventions — the SDK detects entities via AST analysis on `export default defineEntity(...)` calls regardless of where the file lives. @@ -47,15 +60,18 @@ Both Twenty SDK packages belong under `devDependencies`, not `dependencies`: { "dependencies": {}, "devDependencies": { - "twenty-client-sdk": "^2.13.0", - "twenty-sdk": "^2.13.0" + "twenty-client-sdk": "2.20.0", + "twenty-sdk": "2.20.0", + "twenty-ui": "1.0.0-alpha.1" } } ``` +The scaffolder pins `twenty-sdk` and `twenty-client-sdk` to its own version — keep the two in sync when upgrading. + - **`twenty-sdk`** ships the `twenty` CLI and the build/scaffolding tooling. It only runs at development and build time and is never imported by your published app's runtime. - **`twenty-client-sdk`** _is_ imported by your app code (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), but Twenty provides it at runtime — logic functions get it from a generated SDK layer, and front components resolve it from server-served modules. Your installed copy is only used for typechecking and the deploy-time build, so it never needs to ship in the deployed bundle. -Keeping either package under `dependencies` pulls it into the installed app's runtime bundle, where it is dead weight. `twenty build` emits a warning when either is still listed under `dependencies`. +Keeping either package under `dependencies` pulls it into the installed app's runtime bundle, where it is dead weight. `twenty dev:build` emits a warning when either is still listed under `dependencies`. Add your app's own runtime dependencies (libraries your logic functions actually import at runtime) under `dependencies` as usual. diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx index 09cdeb43b3..796a140129 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx @@ -5,10 +5,10 @@ icon: "wrench" --- - **Docker errors** — Make sure Docker Desktop (or the daemon) is running before `yarn twenty docker:start`. The error message will show the right start command for your OS. -- **Wrong Node version** — Need 24+. Check with `node -v`. +- **Wrong Node version** — Need 24.5+ (`engines.node: ^24.5.0`). Check with `node -v`. - **Yarn 4 missing** — Run `corepack enable`. - **Dependencies broken** — `rm -rf node_modules && yarn install`. - **`twenty-sdk` errors after upgrading to v2.8.0** — It moved from `dependencies` to `devDependencies` in v2.8.0. See [Project Structure → Dependencies](/developers/extend/apps/getting-started/project-structure#dependencies). -- **`twenty build` warns about `twenty-client-sdk` under `dependencies`** — It is provided at runtime by Twenty, so it should be moved to `devDependencies` alongside `twenty-sdk`. See [Project Structure → Dependencies](/developers/extend/apps/getting-started/project-structure#dependencies). +- **`twenty dev:build` warns about `twenty-client-sdk` under `dependencies`** — It is provided at runtime by Twenty, so it should be moved to `devDependencies` alongside `twenty-sdk`. See [Project Structure → Dependencies](/developers/extend/apps/getting-started/project-structure#dependencies). Stuck? Ask on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-docs/developers/extend/apps/operations/testing.mdx b/packages/twenty-docs/developers/extend/apps/operations/testing.mdx index 3d0cc24deb..ee784aa022 100644 --- a/packages/twenty-docs/developers/extend/apps/operations/testing.mdx +++ b/packages/twenty-docs/developers/extend/apps/operations/testing.mdx @@ -78,6 +78,13 @@ Create a `vitest.config.ts` at the root of your app: import tsconfigPaths from 'vite-tsconfig-paths'; import { defineConfig } from 'vitest/config'; +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? ''; + +// Make env vars available to globalSetup (test.env only applies to workers) +process.env.TWENTY_API_URL = TWENTY_API_URL; +process.env.TWENTY_API_KEY = TWENTY_API_KEY; + export default defineConfig({ plugins: [ tsconfigPaths({ @@ -88,54 +95,61 @@ export default defineConfig({ test: { testTimeout: 120_000, hookTimeout: 120_000, + fileParallelism: false, include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], + globalSetup: ['src/__tests__/global-setup.ts'], env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', + TWENTY_API_URL, + TWENTY_API_KEY, }, }, }); ``` -Create a setup file that verifies the server is reachable before tests run: +Create a global setup file that verifies the server is reachable, writes a test config for the SDK (`~/.twenty/config.test.json`), and syncs the app before tests run: -```ts src/__tests__/setup-test.ts +```ts src/__tests__/global-setup.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'); +import { appDevOnce, appUninstall } from 'twenty-sdk/cli'; + +const APP_PATH = process.cwd(); +const CONFIG_DIR = path.join(os.homedir(), '.twenty'); + +export async function setup() { + const apiUrl = process.env.TWENTY_API_URL!; + const apiKey = process.env.TWENTY_API_KEY!; -beforeAll(async () => { // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - + const response = await fetch(`${apiUrl}/healthz`); if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); + throw new Error(`Twenty server is not reachable at ${apiUrl}.`); } - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - + // Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test) + fs.mkdirSync(CONFIG_DIR, { recursive: true }); fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), + path.join(CONFIG_DIR, 'config.test.json'), JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, + remotes: { local: { apiUrl, apiKey } }, defaultRemote: 'local', }, null, 2), ); -}); + + // Start from a clean slate, then sync the app + await appUninstall({ appPath: APP_PATH }).catch(() => {}); + + const result = await appDevOnce({ appPath: APP_PATH }); + if (!result.success) { + throw new Error(`Dev sync failed: ${result.error?.message}`); + } +} + +export async function teardown() { + await appUninstall({ appPath: APP_PATH }); +} ``` ## Programmatic SDK APIs @@ -146,6 +160,7 @@ The `twenty-sdk/cli` subpath exports functions you can call directly from test c |----------|-------------| | `appBuild` | Build the app and optionally pack a tarball | | `appDeploy` | Upload a tarball to the server | +| `appDevOnce` | Build and sync the app once (same as `yarn twenty apply`) | | `appInstall` | Install the app on the active workspace | | `appUninstall` | Uninstall the app from the active workspace | @@ -238,64 +253,10 @@ You can also run type checking on your app without running tests: yarn twenty dev:typecheck ``` -This runs `tsc --noEmit` and reports any type errors. +This runs `tsc --noEmit` against your app's `tsconfig.json` and reports any type errors. Scaffolded apps also ship a `yarn typecheck` script that covers test files too (`tsconfig.spec.json`). ## CI with GitHub Actions -The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests. +The scaffolder generates a ready-to-use workflow at `.github/workflows/ci.yml`. On every push to `main` and every pull request, it spawns an ephemeral Twenty server in the runner (via the `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test` action), then runs `yarn lint`, `yarn typecheck`, `yarn test:unit`, and `yarn test` with `TWENTY_API_URL` / `TWENTY_API_KEY` pointing at that server. No secrets are required, and you can pin the server version via the `TWENTY_VERSION` env at the top of the workflow. -The workflow: - -1. Checks out your code -2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action -3. Installs dependencies with `yarn install --immutable` -4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs - -```yaml .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 }} -``` - -You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub. - -To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow. +See [Publishing → Automated CI/CD](/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) for a full walkthrough of both scaffolded workflows (`ci.yml` and the `cd.yml` deploy pipeline).