Various SDK improvements (#18115)

## Summary

- **Refactor frontend metadata loading architecture**: Split the
monolithic `EagerMetadataLoadEffect` into focused provider effects
(`UserMetadataProviderEffect`, `ObjectMetadataProviderEffect`,
`ViewMetadataProviderEffect`) orchestrated by `MetadataProviderEffects`.
Replaced `UserProvider` + `ObjectMetadataItemsProvider` with a single
`MetadataGater` that gates rendering on `isAppMetadataReadyState`. The
metadata store now validates view-object consistency before promoting
views, and `updateDraft` skips no-op updates via deep equality checks.

- **SDK CLI improvements**: Added `app:typecheck` command, improved
error handling in API sync (extracts GraphQL error messages), added
`serializeError` utility for human-readable error output, added `error`
file status to dev mode orchestrator with UI support, and fixed
ClickHouse migration/seed commands to use `transpile-only`.
This commit is contained in:
Charles Bochet
2026-02-23 19:57:02 +01:00
committed by GitHub
parent ccddd105d8
commit 0d4fe4575b
38 changed files with 675 additions and 450 deletions
@@ -40,7 +40,8 @@ export type OrchestratorStateFileStatus =
| 'pending'
| 'building'
| 'uploading'
| 'success';
| 'success'
| 'error';
export type OrchestratorStateEntityInfo = {
name: string;
@@ -81,10 +82,11 @@ const FILE_STATUS_TRANSITION_MATRIX: Record<
OrchestratorStateFileStatus,
OrchestratorStateFileStatus[]
> = {
pending: ['building', 'uploading', 'success'],
building: ['pending', 'uploading', 'success'],
uploading: ['pending', 'success'],
success: ['pending', 'building', 'uploading'],
pending: ['building', 'uploading', 'success', 'error'],
building: ['pending', 'uploading', 'success', 'error'],
uploading: ['pending', 'success', 'error'],
success: ['pending', 'building', 'uploading', 'error'],
error: ['pending', 'building', 'uploading', 'success'],
};
export class OrchestratorState {
@@ -13,6 +13,7 @@ import {
} from '@/cli/utilities/dev/orchestrator/steps/start-watchers-orchestrator-step';
import { SyncApplicationOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/sync-application-orchestrator-step';
import { UploadFilesOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/upload-files-orchestrator-step';
import { serializeError } from '@/cli/utilities/error/serialize-error';
import * as fs from 'fs-extra';
import path from 'path';
import { OUTPUT_DIR, type Manifest } from 'twenty-shared/application';
@@ -143,10 +144,11 @@ export class DevModeOrchestrator {
await this.runSyncPipeline();
} catch (error) {
this.state.addEvent({
message: `Sync failed with error ${JSON.stringify(error, null, 2)}`,
message: `Sync failed with error: ${serializeError(error)}`,
status: 'error',
});
this.state.updatePipeline({ status: 'error' });
this.state.updateAllEntitiesStatus('error');
} finally {
this.state.updatePipeline({ isSyncing: false });
}
@@ -7,6 +7,7 @@ import {
type OrchestratorStateStepEvent,
type OrchestratorStateSyncStatus,
} from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator-state';
import { serializeError } from '@/cli/utilities/error/serialize-error';
import { type Manifest } from 'twenty-shared/application';
export type SyncApplicationOrchestratorStepOutput = {
@@ -73,12 +74,13 @@ export class SyncApplicationOrchestratorStep {
return;
}
const errorMessage = `Sync failed with error ${JSON.stringify(syncResult.error, null, 2)}`;
const errorMessage = `Sync failed with error: ${serializeError(syncResult.error)}`;
events.push({ message: errorMessage, status: 'error' });
step.output = { syncStatus: 'error', error: errorMessage };
step.status = 'error';
this.state.updatePipeline({ status: 'error', error: errorMessage });
this.state.updateAllEntitiesStatus('error');
this.state.applyStepEvents(events);
}
}
@@ -92,7 +92,11 @@ export const DevUiEntityLegend = (): React.ReactElement => {
<Text color={DEV_UI_STATUS_CONFIG.done.color}>
{DEV_UI_STATUS_CONFIG.done.icon}
</Text>{' '}
success
success{' '}
<Text color={DEV_UI_STATUS_CONFIG.error.color}>
{DEV_UI_STATUS_CONFIG.error.icon}
</Text>{' '}
error
</Text>
</Box>
);
@@ -49,6 +49,7 @@ export const mapFileStatusToDevUiStatus = (
building: 'in_progress',
uploading: 'uploading',
success: 'done',
error: 'error',
};
return mapping[status];
@@ -182,6 +183,7 @@ export const getPipelineRows = (
): DevUiPipelineRow[] => {
const entities = [...state.entities.values()];
const hasError = entities.some((entity) => entity.status === 'error');
const isBuilding = entities.some((entity) => entity.status === 'building');
const allUploaded =
entities.length > 0 &&
@@ -189,11 +191,13 @@ export const getPipelineRows = (
(entity) => entity.status === 'uploading' || entity.status === 'success',
);
const resourcesBuildStatus: OrchestratorStateStepStatus = isBuilding
? 'in_progress'
: allUploaded
? 'done'
: 'idle';
const resourcesBuildStatus: OrchestratorStateStepStatus = hasError
? 'error'
: isBuilding
? 'in_progress'
: allUploaded
? 'done'
: 'idle';
return [
{