Add Client Api generation (#17961)

## Add API client generation to SDK dev mode and refactor orchestrator
into step-based pipeline

### Why

The SDK dev mode lacked typed API client generation, forcing developers
to work without auto-generated GraphQL types when building applications.
Additionally, the orchestrator was a monolithic class that mixed watcher
management, token handling, and sync logic — making it difficult to
extend with new steps like client generation.

### How

- **Refactored the orchestrator** into a step-based pipeline with
dedicated classes: `CheckServer`, `EnsureValidTokens`,
`ResolveApplication`, `BuildManifest`, `UploadFiles`,
`GenerateApiClient`, `SyncApplication`, and `StartWatchers`. Each step
has typed input/output/status, managed by a new `OrchestratorState`
class.
- **Added `GenerateApiClientOrchestratorStep`** that detects
object/field schema changes and regenerates a typed GraphQL client (via
`@genql/cli`) into `node_modules/twenty-sdk/generated` for seamless
imports.
- **Replaced `checkApplicationExist`** with `findOneApplication` on both
server resolver and SDK API service, returning the entity data instead
of a boolean.
- **Added application token pair mutations**
(`generateApplicationToken`, `renewApplicationToken`) to the API
service, with the server now returning `ApplicationTokenPairDTO`
containing both access and refresh tokens.
- **Restructured the dev UI** into `dev/ui/components/` with dedicated
panel, section, and event log components.
- **Simplified `AppDevCommand`** from ~180 lines of watcher management
down to ~40 lines that delegate entirely to the orchestrator.
This commit is contained in:
Charles Bochet
2026-02-17 18:45:52 +01:00
committed by GitHub
parent 0891886aa0
commit c0cc0689d6
72 changed files with 2419 additions and 1422 deletions
@@ -0,0 +1,33 @@
import { useState, useEffect } from 'react';
import {
type DevUiStatus,
DEV_UI_STATUS_CONFIG,
SPINNER_FRAMES,
UPLOAD_FRAMES,
} from '@/cli/utilities/dev/ui/dev-ui-constants';
export const useAnimatedFrame = (frames: string[], interval = 80): string => {
const [frameIndex, setFrameIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setFrameIndex((currentIndex) => (currentIndex + 1) % frames.length);
}, interval);
return () => clearInterval(timer);
}, [frames, interval]);
return frames[frameIndex];
};
export const useStatusIcon = (uiStatus: DevUiStatus): string => {
const spinnerFrame = useAnimatedFrame(SPINNER_FRAMES, 80);
const uploadFrame = useAnimatedFrame(UPLOAD_FRAMES, 200);
const config = DEV_UI_STATUS_CONFIG[uiStatus];
if (config.icon === 'spinner') return spinnerFrame;
if (config.icon === 'upload') return uploadFrame;
return config.icon;
};