From 7c661f47fd56fccbc5110fc3fda58a5234e09a05 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:43:43 +0200 Subject: [PATCH] e2e test environment for`twenty-cli` (#15123) # Introduction Defining very first basis of the twenty-cli e2e testing env. Dynamically generating tests cases based on a list of applications names that will be matched to stored twenty-apps and run install delete and reinstall with their configuration on the same instance We could use a glob pattern with a specific e2e configuration in every apps but right now overkill ## Notes - We should define typescript path aliasing to ease import devxp - parse the config using a zod object ## Some vision on test granularity Right now we only check that the server sent back success or failure on below operation. In the future the synchronize will return a report of what has been installed per entity exactly. We will be able to snapshot everything in order to detect regressions We should also be testing the cli directly for init and other stuff in the end, we could get some inspiration from what's done in preconstruct e2e tests with an on heap virtual file system ## Conclusion Any suggestions are more than welcomed ! close https://github.com/twentyhq/core-team-issues/issues/1721 --- .github/workflows/ci-cli.yaml | 44 +++++++++++++++- packages/twenty-cli/.env.e2e | 4 ++ packages/twenty-cli/jest.e2e.config.ts | 48 +++++++++++++++++ packages/twenty-cli/package.json | 8 ++- packages/twenty-cli/project.json | 35 +++++++++++-- ...tions-install-delete-reinstall.e2e-spec.ts | 52 +++++++++++++++++++ .../covered-applications-folder.constant.ts | 1 + .../e2e/constants/server-url.constant.ts | 1 + .../src/__tests__/e2e/health.e2e-spec.ts | 21 ++++++++ .../twenty-cli/src/__tests__/e2e/teardown.ts | 14 +++++ .../utils/get-tested-application-path.util.ts | 12 +++++ .../src/commands/app-delete.command.ts | 24 +++++---- .../src/commands/app-sync.command.ts | 17 +++--- .../twenty-cli/src/commands/app.command.ts | 30 ++++++++--- .../twenty-cli/src/services/config.service.ts | 6 ++- .../src/utils/app-manifest-loader.ts | 2 +- packages/twenty-cli/tsconfig.e2e.json | 31 +++++++++++ packages/twenty-cli/tsconfig.json | 21 ++------ packages/twenty-cli/tsconfig.lib.json | 21 ++++++++ packages/twenty-server/project.json | 7 +++ yarn.lock | 3 +- 21 files changed, 353 insertions(+), 49 deletions(-) create mode 100644 packages/twenty-cli/.env.e2e create mode 100644 packages/twenty-cli/jest.e2e.config.ts create mode 100644 packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts create mode 100644 packages/twenty-cli/src/__tests__/e2e/constants/covered-applications-folder.constant.ts create mode 100644 packages/twenty-cli/src/__tests__/e2e/constants/server-url.constant.ts create mode 100644 packages/twenty-cli/src/__tests__/e2e/health.e2e-spec.ts create mode 100644 packages/twenty-cli/src/__tests__/e2e/teardown.ts create mode 100644 packages/twenty-cli/src/__tests__/e2e/utils/get-tested-application-path.util.ts create mode 100644 packages/twenty-cli/tsconfig.e2e.json create mode 100644 packages/twenty-cli/tsconfig.lib.json diff --git a/.github/workflows/ci-cli.yaml b/.github/workflows/ci-cli.yaml index c357e0a82c0..80848461293 100644 --- a/.github/workflows/ci-cli.yaml +++ b/.github/workflows/ci-cli.yaml @@ -20,6 +20,7 @@ jobs: with: files: | packages/twenty-cli/** + packages/twenty-server/** cli-test: needs: changed-files-check if: needs.changed-files-check.outputs.any_changed == 'true' @@ -44,11 +45,52 @@ jobs: with: tag: scope:cli tasks: ${{ matrix.task }} + cli-e2e-test: + timeout-minutes: 30 + runs-on: depot-ubuntu-24.04-8 + needs: [changed-files-check, cli-test] + if: needs.changed-files-check.outputs.any_changed == 'true' + services: + postgres: + image: twentycrm/twenty-postgres-spilo + env: + PGUSER_SUPERUSER: postgres + PGPASSWORD_SUPERUSER: postgres + ALLOW_NOSSL: 'true' + SPILO_PROVIDER: 'local' + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + redis: + image: redis + ports: + - 6379:6379 + env: + NODE_ENV: test + steps: + - name: Fetch custom Github Actions and base branch history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install dependencies + uses: ./.github/workflows/actions/yarn-install + - name: Server / Create Test DB + run: | + PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";' + - name: CLI / Run E2E Tests + uses: ./.github/workflows/actions/nx-affected + with: + tag: scope:cli + tasks: 'test:e2e' ci-cli-status-check: if: always() && !cancelled() timeout-minutes: 5 runs-on: ubuntu-latest - needs: [changed-files-check, cli-test] + needs: [changed-files-check, cli-test, cli-e2e-test] steps: - name: Fail job if any needs failed if: contains(needs.*.result, 'failure') diff --git a/packages/twenty-cli/.env.e2e b/packages/twenty-cli/.env.e2e new file mode 100644 index 00000000000..0f92e2be146 --- /dev/null +++ b/packages/twenty-cli/.env.e2e @@ -0,0 +1,4 @@ +TWENTY_API_URL=http://localhost:3000 +# APPLE_JANE_ADMIN_ACCESS_TOKEN +TWENTY_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik +TWENTY_DEFAULT_APP=e2e-default-app diff --git a/packages/twenty-cli/jest.e2e.config.ts b/packages/twenty-cli/jest.e2e.config.ts new file mode 100644 index 00000000000..a5c8c433964 --- /dev/null +++ b/packages/twenty-cli/jest.e2e.config.ts @@ -0,0 +1,48 @@ +import { type JestConfigWithTsJest } from 'ts-jest'; + +const jestConfig: JestConfigWithTsJest = { + // For more information please have a look to official docs https://jestjs.io/docs/configuration/#prettierpath-string + // Prettier v3 should be supported in jest v30 https://github.com/jestjs/jest/releases/tag/v30.0.0-alpha.1 + prettierPath: null, + displayName: 'twenty-cli-e2e', + silent: false, + errorOnDeprecated: true, + maxConcurrency: 1, + moduleFileExtensions: ['js', 'json', 'ts'], + rootDir: '.', + testEnvironment: 'node', + testRegex: '\\.e2e-spec\\.ts$', + modulePathIgnorePatterns: ['/dist'], + globalTeardown: '/src/__tests__/e2e/teardown.ts', + testTimeout: 30000, // 30 seconds timeout for e2e tests + maxWorkers: 1, + transform: { + '^.+\\.(t|j)s$': [ + '@swc/jest', + { + jsc: { + parser: { + syntax: 'typescript', + tsx: false, + decorators: true, + }, + transform: { + decoratorMetadata: true, + }, + baseUrl: '.', + paths: { + 'src/*': ['./src/*'], + }, + }, + }, + ], + }, + transformIgnorePatterns: [ + 'node_modules/(?!(chalk|inquirer|@inquirer|ansi-styles|strip-ansi|has-flag|supports-color|color-convert|color-name|wrap-ansi|string-width|is-fullwidth-code-point|emoji-regex|onetime|mimic-fn|signal-exit|yallist|lru-cache|p-limit|p-queue|p-timeout|p-finally|p-try|p-cancelable|p-locate|p-map|p-race|p-reduce|p-some|p-waterfall|p-defer|p-delay|p-retry|p-any|p-settle|p-all|p-map-series|p-map-concurrent|p-filter|p-reject|p-tap|p-log|p-debounce|p-throttle|p-forever|p-whilst|p-do-whilst|p-until|p-wait-for|p-min-delay)/)', + ], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, +}; + +export default jestConfig; diff --git a/packages/twenty-cli/package.json b/packages/twenty-cli/package.json index e2188757cfa..83714984894 100644 --- a/packages/twenty-cli/package.json +++ b/packages/twenty-cli/package.json @@ -6,6 +6,11 @@ "bin": { "twenty": "dist/cli.js" }, + "files": [ + "dist/**/*", + "!dist/**/*.e2e-spec.*", + "!dist/**/__tests__/**" + ], "scripts": { "build": "echo 'use npx nx build'", "dev": "tsx src/cli.ts", @@ -40,7 +45,8 @@ "@types/node": "^20.0.0", "jest": "^29.5.0", "tsx": "^4.7.0", - "typescript": "^5.3.0" + "typescript": "^5.3.0", + "wait-on": "^7.2.0" }, "engines": { "node": "^24.5.0", diff --git a/packages/twenty-cli/project.json b/packages/twenty-cli/project.json index 285e23b4ce9..065bbb05020 100644 --- a/packages/twenty-cli/project.json +++ b/packages/twenty-cli/project.json @@ -9,7 +9,7 @@ "cache": true, "options": { "cwd": "packages/twenty-cli", - "commands": ["rimraf dist", "tsc"] + "commands": ["rimraf dist", "tsc --project tsconfig.lib.json"] }, "dependsOn": ["^after-build"] }, @@ -18,7 +18,9 @@ "cache": true, "options": { "cwd": "packages/twenty-cli", - "commands": ["cp -R src/constants/base-application-project dist/constants"] + "commands": [ + "cp -R src/constants/base-application-project dist/constants" + ] }, "dependsOn": ["after-build"] }, @@ -42,7 +44,7 @@ "executor": "nx:run-commands", "options": { "cwd": "packages/twenty-cli", - "command": "tsc --noEmit" + "command": "tsc --noEmit --project tsconfig.lib.json" } }, "lint": { @@ -51,7 +53,7 @@ "maxWarnings": 0 }, "configurations": { - "ci": { + "ci": { "lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"], "maxWarnings": 0 }, @@ -71,6 +73,31 @@ "watchAll": false } } + }, + "test:e2e": { + "executor": "nx:run-commands", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "cwd": "packages/twenty-cli", + "commands": [ + "npx wait-on http://localhost:3000/healthz --timeout 600000 --interval 1000 --verbose && NODE_ENV=test npx jest --config ./jest.e2e.config.ts" + ] + }, + "parallel": false, + "dependsOn": [ + { + "target": "build", + "projects": "dependencies" + }, + { + "target": "database:reset", + "projects": "twenty-server" + }, + { + "target": "start:ci-if-needed", + "projects": "twenty-server" + } + ] } } } diff --git a/packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts b/packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts new file mode 100644 index 00000000000..94d425efa28 --- /dev/null +++ b/packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts @@ -0,0 +1,52 @@ +import { existsSync } from 'fs'; +import { AppDeleteCommand } from '../../commands/app-delete.command'; +import { AppSyncCommand } from '../../commands/app-sync.command'; +import { COVERED_APPLICATION_FOLDERS } from './constants/covered-applications-folder.constant'; +import { getTestedApplicationPath } from './utils/get-tested-application-path.util'; + +describe.each(COVERED_APPLICATION_FOLDERS)( + 'Application: "%s" install delete and reinstall test suite', + (applicationName) => { + const syncCommand = new AppSyncCommand(); + const deleteCommand = new AppDeleteCommand(); + const appPath = getTestedApplicationPath(applicationName); + + beforeAll(async () => { + expect(existsSync(appPath)).toBe(true); + }); + + // TODO uncomment when reinstall is fix to cleanup gracefully + // afterAll(async () => { + // const result = await deleteCommand.execute({ + // appPath, + // askForConfirmation: false, + // }); + + // expect(result.success).toBe(true); + // }); + + it(`should successfully install ${applicationName} application`, async () => { + const result = await syncCommand.execute(appPath); + + expect(result.success).toBe(true); + }); + + it(`should successfully delete ${applicationName} application`, async () => { + const result = await deleteCommand.execute({ + appPath, + askForConfirmation: false, + }); + + expect(result.success).toBe(true); + }); + + it.failing( + `should successfully re-install ${applicationName} application`, + async () => { + const result = await syncCommand.execute(appPath); + + expect(result.success).toBe(true); + }, + ); + }, +); diff --git a/packages/twenty-cli/src/__tests__/e2e/constants/covered-applications-folder.constant.ts b/packages/twenty-cli/src/__tests__/e2e/constants/covered-applications-folder.constant.ts new file mode 100644 index 00000000000..569ce1bd826 --- /dev/null +++ b/packages/twenty-cli/src/__tests__/e2e/constants/covered-applications-folder.constant.ts @@ -0,0 +1 @@ +export const COVERED_APPLICATION_FOLDERS = ['hello-world'] as const; diff --git a/packages/twenty-cli/src/__tests__/e2e/constants/server-url.constant.ts b/packages/twenty-cli/src/__tests__/e2e/constants/server-url.constant.ts new file mode 100644 index 00000000000..5e8aa7f2818 --- /dev/null +++ b/packages/twenty-cli/src/__tests__/e2e/constants/server-url.constant.ts @@ -0,0 +1 @@ +export const SERVER_URL = 'http://localhost:3000'; diff --git a/packages/twenty-cli/src/__tests__/e2e/health.e2e-spec.ts b/packages/twenty-cli/src/__tests__/e2e/health.e2e-spec.ts new file mode 100644 index 00000000000..f7f012e88d8 --- /dev/null +++ b/packages/twenty-cli/src/__tests__/e2e/health.e2e-spec.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; +import { ConfigService } from '../../services/config.service'; +import { SERVER_URL } from './constants/server-url.constant'; + +describe('Twenty Server Health Check (E2E)', () => { + const configService = new ConfigService(); + const HEALTH_ENDPOINT = `${SERVER_URL}/healthz`; + + it('should assert e2e configuration is loaded by default', async () => { + const configuration = await configService.getConfig(); + expect(configuration).toMatchObject({ + defaultApp: 'e2e-default-app', + }); + }); + + it('should return 200 for health', async () => { + const response = await axios.get(HEALTH_ENDPOINT); + expect(response.status).toBe(200); + expect(response.data).toBeDefined(); + }); +}); diff --git a/packages/twenty-cli/src/__tests__/e2e/teardown.ts b/packages/twenty-cli/src/__tests__/e2e/teardown.ts new file mode 100644 index 00000000000..f5a431d9b5d --- /dev/null +++ b/packages/twenty-cli/src/__tests__/e2e/teardown.ts @@ -0,0 +1,14 @@ +import { exec } from 'child_process'; + +export default async function globalTeardown() { + return new Promise((resolve) => { + exec('pkill -f "nest start" || true', (error: unknown) => { + if (error) { + console.log('No server processes to kill'); + } else { + console.log('✅ Server processes cleaned up'); + } + resolve(); + }); + }); +} diff --git a/packages/twenty-cli/src/__tests__/e2e/utils/get-tested-application-path.util.ts b/packages/twenty-cli/src/__tests__/e2e/utils/get-tested-application-path.util.ts new file mode 100644 index 00000000000..e00b351e302 --- /dev/null +++ b/packages/twenty-cli/src/__tests__/e2e/utils/get-tested-application-path.util.ts @@ -0,0 +1,12 @@ +import path from 'path'; + +export const getTestedApplicationPath = (relativePath: string): string => { + const currentFileDir = __dirname; + + const twentyAppsPath = path.resolve( + currentFileDir, + '../../../../../twenty-apps', + ); + + return path.join(twentyAppsPath, relativePath); +}; diff --git a/packages/twenty-cli/src/commands/app-delete.command.ts b/packages/twenty-cli/src/commands/app-delete.command.ts index 8e782b57e24..5216343b791 100644 --- a/packages/twenty-cli/src/commands/app-delete.command.ts +++ b/packages/twenty-cli/src/commands/app-delete.command.ts @@ -1,21 +1,26 @@ -import inquirer from 'inquirer'; import chalk from 'chalk'; -import { ApiService } from '../services/api.service'; +import inquirer from 'inquirer'; import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory'; +import { ApiService } from '../services/api.service'; +import { ApiResponse } from '../types/config.types'; import { loadManifest } from '../utils/app-manifest-loader'; export class AppDeleteCommand { private apiService = new ApiService(); - async execute(): Promise { + async execute({ + appPath = CURRENT_EXECUTION_DIRECTORY, + askForConfirmation, + }: { + appPath?: string; + askForConfirmation: boolean; + }): Promise> { try { - const appPath = CURRENT_EXECUTION_DIRECTORY; - console.log(chalk.blue('🚀 Deleting Twenty Application')); console.log(chalk.gray(`📁 App Path: ${appPath}`)); console.log(''); - if (!(await this.confirmationPrompt())) { + if (askForConfirmation && !(await this.confirmationPrompt())) { console.error(chalk.red('⛔️ Aborting deletion')); process.exit(1); } @@ -26,16 +31,17 @@ export class AppDeleteCommand { if (!result.success) { console.error(chalk.red('❌ Deletion failed:'), result.error); - process.exit(1); + } else { + console.log(chalk.green('✅ Application deleted successfully')); } - console.log(chalk.green('✅ Application deleted successfully')); + return result; } catch (error) { console.error( chalk.red('Deletion failed:'), error instanceof Error ? error.message : error, ); - process.exit(1); + throw error; } } diff --git a/packages/twenty-cli/src/commands/app-sync.command.ts b/packages/twenty-cli/src/commands/app-sync.command.ts index 9b48676dda8..413581a7e69 100644 --- a/packages/twenty-cli/src/commands/app-sync.command.ts +++ b/packages/twenty-cli/src/commands/app-sync.command.ts @@ -1,15 +1,17 @@ import chalk from 'chalk'; -import { ApiService } from '../services/api.service'; import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory'; +import { ApiService } from '../services/api.service'; +import { ApiResponse } from '../types/config.types'; import { loadManifest } from '../utils/app-manifest-loader'; export class AppSyncCommand { private apiService = new ApiService(); - async execute(): Promise { + // TODO improve typing + async execute( + appPath: string = CURRENT_EXECUTION_DIRECTORY, + ): Promise> { try { - const appPath = CURRENT_EXECUTION_DIRECTORY; - console.log(chalk.blue('🚀 Syncing Twenty Application')); console.log(chalk.gray(`📁 App Path: ${appPath}`)); console.log(''); @@ -24,16 +26,17 @@ export class AppSyncCommand { if (!result.success) { console.error(chalk.red('❌ Sync failed:'), result.error); - process.exit(1); + } else { + console.log(chalk.green('✅ Application synced successfully')); } - console.log(chalk.green('✅ Application synced successfully')); + return result; } catch (error) { console.error( chalk.red('Sync failed:'), error instanceof Error ? error.message : error, ); - process.exit(1); + throw error; } } } diff --git a/packages/twenty-cli/src/commands/app.command.ts b/packages/twenty-cli/src/commands/app.command.ts index b5116684944..06f09c5b7a7 100644 --- a/packages/twenty-cli/src/commands/app.command.ts +++ b/packages/twenty-cli/src/commands/app.command.ts @@ -1,14 +1,14 @@ +import chalk from 'chalk'; import { Command } from 'commander'; -import { AppSyncCommand } from './app-sync.command'; -import { AppDevCommand } from './app-dev.command'; -import { AppInitCommand } from './app-init.command'; -import { AppDeleteCommand } from './app-delete.command'; import { AppAddCommand, isSyncableEntity, SyncableEntity, } from './app-add.command'; -import chalk from 'chalk'; +import { AppDeleteCommand } from './app-delete.command'; +import { AppDevCommand } from './app-dev.command'; +import { AppInitCommand } from './app-init.command'; +import { AppSyncCommand } from './app-sync.command'; export class AppCommand { private devCommand = new AppDevCommand(); @@ -33,14 +33,30 @@ export class AppCommand { .command('sync') .description('Sync application to Twenty') .action(async () => { - await this.syncCommand.execute(); + try { + const result = await this.syncCommand.execute(); + if (!result.success) { + process.exit(1); + } + } catch { + process.exit(1); + } }); appCommand .command('delete') .description('Delete application from Twenty') .action(async () => { - await this.deleteCommand.execute(); + try { + const result = await this.deleteCommand.execute({ + askForConfirmation: true, + }); + if (!result.success) { + process.exit(1); + } + } catch { + process.exit(1); + } }); appCommand diff --git a/packages/twenty-cli/src/services/config.service.ts b/packages/twenty-cli/src/services/config.service.ts index ccc9d0fcb57..8707efcd18a 100644 --- a/packages/twenty-cli/src/services/config.service.ts +++ b/packages/twenty-cli/src/services/config.service.ts @@ -14,7 +14,10 @@ export class ConfigService { private loadEnvironmentVariables(): void { // Load local .env file if it exists in current working directory - const localEnvPath = path.join(process.cwd(), '.env'); + const localEnvPath = path.join( + process.cwd(), + process.env.NODE_ENV === 'test' ? '.env.e2e' : '.env', + ); if (fs.existsSync(localEnvPath)) { loadDotenv({ path: localEnvPath }); } @@ -38,6 +41,7 @@ export class ConfigService { if (configExists) { const configContent = await fs.readFile(this.configPath, 'utf8'); + // TODO parse using a zod schema fileConfig = JSON.parse(configContent || '{}'); } diff --git a/packages/twenty-cli/src/utils/app-manifest-loader.ts b/packages/twenty-cli/src/utils/app-manifest-loader.ts index e5a85d5895e..610206708c9 100644 --- a/packages/twenty-cli/src/utils/app-manifest-loader.ts +++ b/packages/twenty-cli/src/utils/app-manifest-loader.ts @@ -6,8 +6,8 @@ import { CoreEntityManifest, PackageJson, } from '../types/config.types'; -import { parseJsoncFile } from './jsonc-parser'; import { validateSchema } from '../utils/schema-validator'; +import { parseJsoncFile } from './jsonc-parser'; type Sources = { [key: string]: string | Sources }; diff --git a/packages/twenty-cli/tsconfig.e2e.json b/packages/twenty-cli/tsconfig.e2e.json new file mode 100644 index 00000000000..054545417ed --- /dev/null +++ b/packages/twenty-cli/tsconfig.e2e.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist-e2e", + "rootDir": "./src", + "module": "commonjs", + "target": "ES2022", + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": false, + "declarationMap": false, + "sourceMap": true, + "types": ["jest", "node"] + }, + "include": [ + "src/**/*", + "src/**/__tests__/**/*.e2e-spec.ts" + ], + "exclude": [ + "node_modules", + "dist", + "dist-e2e", + "**/*.test.ts", + "**/*.spec.ts" + ] +} diff --git a/packages/twenty-cli/tsconfig.json b/packages/twenty-cli/tsconfig.json index 18450eba6f9..e48d23f545c 100644 --- a/packages/twenty-cli/tsconfig.json +++ b/packages/twenty-cli/tsconfig.json @@ -1,21 +1,8 @@ { - "extends": "../../tsconfig.base.json", + "extends": "./tsconfig.lib.json", "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src", - "module": "commonjs", - "target": "ES2022", - "moduleResolution": "node", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true + "composite": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] -} + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"] +} \ No newline at end of file diff --git a/packages/twenty-cli/tsconfig.lib.json b/packages/twenty-cli/tsconfig.lib.json new file mode 100644 index 00000000000..f03691f8a42 --- /dev/null +++ b/packages/twenty-cli/tsconfig.lib.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "commonjs", + "target": "ES2022", + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"] +} diff --git a/packages/twenty-server/project.json b/packages/twenty-server/project.json index 8b80d32c8f0..6a1d3ae77ca 100644 --- a/packages/twenty-server/project.json +++ b/packages/twenty-server/project.json @@ -57,6 +57,13 @@ "command": "NODE_ENV=development && nest start" } }, + "start:ci-if-needed": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/twenty-server", + "command": "curl -f -s http://localhost:3000/healthz > /dev/null 2>&1 && echo '✅ Server already running' || (echo '🚀 Server not running, starting...' && NODE_ENV=development nohup nest start > server.log 2>&1 &)" + } + }, "start:debug": { "executor": "nx:run-commands", "dependsOn": ["build"], diff --git a/yarn.lock b/yarn.lock index 1d71bbbfbd9..387093de04a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -52806,6 +52806,7 @@ __metadata: lodash.kebabcase: "npm:^4.1.1" tsx: "npm:^4.7.0" typescript: "npm:^5.3.0" + wait-on: "npm:^7.2.0" bin: twenty: dist/cli.js languageName: unknown @@ -55410,7 +55411,7 @@ __metadata: languageName: node linkType: hard -"wait-on@npm:^7.0.0": +"wait-on@npm:^7.0.0, wait-on@npm:^7.2.0": version: 7.2.0 resolution: "wait-on@npm:7.2.0" dependencies: