Fix sdk-e2e-test: ensure DB is ready before server starts (#19583)

## Summary

- The `sdk-e2e-test` CI job has been failing since at least April 9th
because the nx dependency graph runs `database:reset` and
`start:ci-if-needed` in **parallel** — neither depends on the other. The
server starts before the DB tables are created, crashes with `relation
"core.keyValuePair" does not exist`, and `wait-on` times out after 10
minutes.
- Replace the `nx-affected` orchestration for e2e with explicit
**sequential** CI steps: build → create DB → reset DB → start server →
wait for health → run tests.
- Add server log dump on failure for easier future debugging.

## Root cause

In `packages/twenty-sdk/project.json`, the `test:e2e` target has:

```json
"dependsOn": [
  "build",
  { "target": "database:reset", "projects": "twenty-server" },
  { "target": "start:ci-if-needed", "projects": "twenty-server" }
]
```

Since `database:reset` and `start:ci-if-needed` don't depend on each
other, nx can (and does) run them concurrently. `start:ci-if-needed`
fires `nohup nest start &` and immediately completes. The server process
tries to query `core.keyValuePair` before `database:reset` creates it →
crash → wait-on timeout → job failure.
This commit is contained in:
Charles Bochet
2026-04-11 16:02:12 +02:00
committed by GitHub
parent 3b55026452
commit baf2fc4cc9
14 changed files with 290 additions and 211 deletions
@@ -2,15 +2,15 @@ import path from 'path';
import { OUTPUT_DIR, type Manifest } from 'twenty-shared/application';
import { ApiService } from '@/cli/utilities/api/api-service';
import {
ensureAppAccessTokenIsValidOrRefresh,
ensureAppRegistration,
} from '@/cli/utilities/auth';
import { buildApplication } from '@/cli/utilities/build/common/build-application';
import { runTypecheck } from '@/cli/utilities/build/common/typecheck-plugin';
import { buildAndValidateManifest } from '@/cli/utilities/build/manifest/build-and-validate-manifest';
import { manifestUpdateChecksums } from '@/cli/utilities/build/manifest/manifest-update-checksums';
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
import {
ensureValidAppAccessTokenOrRefresh,
exchangeCredentialsForTokens,
} from '@/cli/utilities/auth/resolve-app-access-token';
import { ClientService } from '@/cli/utilities/client/client-service';
import { ConfigService } from '@/cli/utilities/config/config-service';
import { formatSyncErrorEvents } from '@/cli/utilities/dev/orchestrator/steps/format-sync-error-events';
@@ -124,37 +124,14 @@ const innerAppDevOnce = async (
const configService = new ConfigService();
const appAccessToken =
await ensureValidAppAccessTokenOrRefresh(configService);
if (!appAccessToken) {
const createResult = await apiService.createApplicationRegistration({
const { clientId, clientSecret } = await ensureAppRegistration(
apiService,
configService,
{
name: manifest.application.displayName,
universalIdentifier: manifest.application.universalIdentifier,
});
if (!createResult.success) {
return {
success: false,
error: {
code: APP_ERROR_CODES.SYNC_FAILED,
message: `Failed to create app registration: ${serializeError(createResult.error)}`,
},
};
}
const { applicationRegistration, clientSecret } = createResult.data;
await configService.setConfig({
appRegistrationId: applicationRegistration.id,
appRegistrationClientId: applicationRegistration.oAuthClientId,
});
await exchangeCredentialsForTokens(configService, {
clientId: applicationRegistration.oAuthClientId,
clientSecret,
});
}
},
);
const createDevAppResult = await apiService.createDevelopmentApplication({
universalIdentifier: manifest.application.universalIdentifier,
@@ -166,7 +143,7 @@ const innerAppDevOnce = async (
success: false,
error: {
code: APP_ERROR_CODES.SYNC_FAILED,
message: `Failed to create development application: ${serializeError(createDevAppResult.error)}`,
message: `Failed to install development application: ${serializeError(createDevAppResult.error)}`,
},
};
}
@@ -235,13 +212,14 @@ const innerAppDevOnce = async (
};
}
// Generate the CoreApiClient using an APPLICATION_ACCESS token so the
// server returns the app-scoped schema (objects defined by this app).
onProgress?.('Generating API client...');
try {
const appAccessToken =
await ensureValidAppAccessTokenOrRefresh(configService);
const appAccessToken = await ensureAppAccessTokenIsValidOrRefresh(
configService,
{ clientId, clientSecret },
);
const clientService = new ClientService();
await clientService.generateCoreClient({