Fix app:dev CLI by removing deleted createOneApplication mutation (#18460)

## Summary
- The `createOneApplication` GraphQL mutation was removed from the
server during the application architecture refactor (#18432), but the
SDK CLI (`app:dev`, `app:build --sync`) still called it, causing
failures.
- Simplified the SDK to use `syncApplication` (which now internally
creates the `ApplicationEntity` via `ensureApplicationExists`) instead
of a separate create step.
- On first run (clean install), the orchestrator now runs an initial
sync before initializing the file uploader, so file uploads can proceed
(they require the `ApplicationEntity` to exist).

## Test plan
- [x] Typecheck passes for both `twenty-sdk` and `twenty-server`
- [x] `app:dev` tested locally with existing app (finds app, uploads,
syncs)
- [x] `app:dev` tested locally after `app:uninstall` (creates app via
sync, uploads, syncs)
- [x] SDK unit tests pass (23/26 files, 3 pre-existing failures
unrelated)

Made with [Cursor](https://cursor.com)
This commit is contained in:
Félix Malfait
2026-03-06 18:37:54 +01:00
committed by GitHub
parent 2c69102f15
commit 66d93c4d28
53 changed files with 1837 additions and 2170 deletions
@@ -1,7 +1,6 @@
import { type EntityFilePaths } from '@/cli/utilities/build/manifest/manifest-extract-config';
import { type BuildManifestOrchestratorStepOutput } from '@/cli/utilities/dev/orchestrator/steps/build-manifest-orchestrator-step';
import { type CheckServerOrchestratorStepOutput } from '@/cli/utilities/dev/orchestrator/steps/check-server-orchestrator-step';
import { type ResolveApplicationOrchestratorStepOutput } from '@/cli/utilities/dev/orchestrator/steps/resolve-application-orchestrator-step';
import { type StartWatchersOrchestratorStepOutput } from '@/cli/utilities/dev/orchestrator/steps/start-watchers-orchestrator-step';
import { type SyncApplicationOrchestratorStepOutput } from '@/cli/utilities/dev/orchestrator/steps/sync-application-orchestrator-step';
import { type UploadFilesOrchestratorStepOutput } from '@/cli/utilities/dev/orchestrator/steps/upload-files-orchestrator-step';
@@ -96,7 +95,10 @@ export class OrchestratorState {
steps: {
checkServer: OrchestratorStepState<CheckServerOrchestratorStepOutput>;
ensureValidTokens: OrchestratorStepState<Record<string, never>>;
resolveApplication: OrchestratorStepState<ResolveApplicationOrchestratorStepOutput>;
resolveApplication: OrchestratorStepState<{
applicationId: string | null;
universalIdentifier: string | null;
}>;
buildManifest: OrchestratorStepState<BuildManifestOrchestratorStepOutput>;
uploadFiles: OrchestratorStepState<UploadFilesOrchestratorStepOutput>;
generateApiClient: OrchestratorStepState<Record<string, never>>;
@@ -7,7 +7,6 @@ import { CheckServerOrchestratorStep } from '@/cli/utilities/dev/orchestrator/st
import { EnsureValidTokensOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/ensure-valid-tokens-orchestrator-step';
import { GenerateApiClientOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/generate-api-client-orchestrator-step';
import { RegisterAppOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/register-app-orchestrator-step';
import { ResolveApplicationOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/resolve-application-orchestrator-step';
import {
StartWatchersOrchestratorStep,
type FileBuiltEvent,
@@ -30,13 +29,13 @@ export class DevModeOrchestrator {
private syncTimer: NodeJS.Timeout | null = null;
private serverCheckInterval: NodeJS.Timeout | null = null;
private apiService: ApiService;
private clientService: ClientService;
private skipTypecheck = true;
private checkServerStep: CheckServerOrchestratorStep;
private ensureValidTokensStep: EnsureValidTokensOrchestratorStep;
private buildManifestStep: BuildManifestOrchestratorStep;
private registerAppStep: RegisterAppOrchestratorStep;
private resolveApplicationStep: ResolveApplicationOrchestratorStep;
private uploadFilesStep: UploadFilesOrchestratorStep;
private generateApiClientStep: GenerateApiClientOrchestratorStep;
private syncApplicationStep: SyncApplicationOrchestratorStep;
@@ -46,7 +45,8 @@ export class DevModeOrchestrator {
this.debounceMs = options.debounceMs ?? 200;
this.state = options.state;
const apiService = new ApiService({ disableInterceptors: true });
this.apiService = new ApiService({ disableInterceptors: true });
const apiService = this.apiService;
const configService = new ConfigService();
this.clientService = new ClientService();
const stepDeps = { state: this.state, notify: () => this.state.notify() };
@@ -66,10 +66,6 @@ export class DevModeOrchestrator {
apiService,
configService,
});
this.resolveApplicationStep = new ResolveApplicationOrchestratorStep({
...stepDeps,
apiService,
});
this.uploadFilesStep = new UploadFilesOrchestratorStep(stepDeps);
this.generateApiClientStep = new GenerateApiClientOrchestratorStep({
...stepDeps,
@@ -223,20 +219,37 @@ export class DevModeOrchestrator {
}
private async initializePipeline(manifest: Manifest): Promise<boolean> {
const registerResult = await this.registerAppStep.execute({ manifest });
await this.registerAppStep.execute({ manifest });
const resolveResult = await this.resolveApplicationStep.execute({
manifest,
applicationRegistrationId:
registerResult.applicationRegistrationId ?? undefined,
const createResult = await this.apiService.createDevelopmentApplication({
universalIdentifier: manifest.application.universalIdentifier,
name: manifest.application.displayName,
});
if (!resolveResult.applicationId) {
if (!createResult.success || !createResult.data) {
this.state.applyStepEvents([
{
message: 'Failed to create development application',
status: 'error',
},
]);
this.state.updatePipeline({ status: 'error' });
return false;
}
this.state.steps.resolveApplication.output = {
applicationId: createResult.data.id,
universalIdentifier: createResult.data.universalIdentifier,
};
this.state.steps.resolveApplication.status = 'done';
this.state.applyStepEvents([
{ message: 'Application created', status: 'success' },
]);
await this.ensureValidTokensStep.exchangeTokens({
applicationId: resolveResult.applicationId,
applicationId: createResult.data.id,
});
this.uploadFilesStep.initialize({
@@ -1,74 +0,0 @@
import { type ApiService } from '@/cli/utilities/api/api-service';
import { findOrCreateApplication } from '@/cli/utilities/application/find-or-create-application';
import { type OrchestratorState } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator-state';
import { type Manifest } from 'twenty-shared/application';
export type ResolveApplicationOrchestratorStepOutput = {
applicationId: string | null;
universalIdentifier: string | null;
};
export class ResolveApplicationOrchestratorStep {
private apiService: ApiService;
private state: OrchestratorState;
private notify: () => void;
constructor({
apiService,
state,
notify,
}: {
apiService: ApiService;
state: OrchestratorState;
notify: () => void;
}) {
this.apiService = apiService;
this.state = state;
this.notify = notify;
}
async execute(input: {
manifest: Manifest;
applicationRegistrationId?: string;
}): Promise<ResolveApplicationOrchestratorStepOutput> {
const step = this.state.steps.resolveApplication;
step.status = 'in_progress';
this.notify();
const result = await findOrCreateApplication({
apiService: this.apiService,
manifest: input.manifest,
applicationRegistrationId: input.applicationRegistrationId,
});
if (!result.success) {
this.state.applyStepEvents([
{
message: result.error,
status: 'error',
},
]);
step.status = 'error';
this.state.updatePipeline({ status: 'error' });
return step.output;
}
if (result.created) {
this.state.applyStepEvents([
{ message: 'Creating application', status: 'info' },
{ message: 'Application created', status: 'success' },
]);
}
step.output = {
applicationId: result.applicationId,
universalIdentifier: result.universalIdentifier,
};
step.status = 'done';
this.notify();
return step.output;
}
}