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:
@@ -16,7 +16,6 @@
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"entity:add": "twenty entity:add",
|
||||
"app:generate": "twenty app:generate",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
icon: 'IconWorld',
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
yarnLockChecksum: 'd41d8cd98f00b204e9800998ecf8427e',
|
||||
packageJsonChecksum: '42c3913415952d91ff1cf67ef6452872',
|
||||
packageJsonChecksum: '2851d0e2c3621a57e1fd103a245b6fde',
|
||||
},
|
||||
frontComponents: [
|
||||
{
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"entity:add": "twenty entity:add",
|
||||
"app:generate": "twenty app:generate",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
description: 'An app with all entities at root level',
|
||||
icon: 'IconFolder',
|
||||
defaultRoleUniversalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000002',
|
||||
packageJsonChecksum: '351460efb13a6c1bee63e27cf87f6ece',
|
||||
packageJsonChecksum: '93ae1e2eb3db18351d06f43550700dcc',
|
||||
yarnLockChecksum: 'd41d8cd98f00b204e9800998ecf8427e',
|
||||
},
|
||||
publicAssets: [],
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"entity:add": "twenty entity:add",
|
||||
"app:generate": "twenty app:generate",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
|
||||
@@ -2,12 +2,30 @@ import { vi } from 'vitest';
|
||||
|
||||
const mockApiService = {
|
||||
validateAuth: vi.fn().mockResolvedValue({ authValid: true, serverUp: true }),
|
||||
checkApplicationExist: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ success: true, data: false }),
|
||||
findOneApplication: vi.fn().mockResolvedValue({ success: true, data: null }),
|
||||
createApplication: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ success: true, data: { id: 'mock-id' } }),
|
||||
generateApplicationToken: vi.fn().mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
applicationAccessToken: { token: 'mock-access-token', expiresAt: '' },
|
||||
applicationRefreshToken: { token: 'mock-refresh-token', expiresAt: '' },
|
||||
},
|
||||
}),
|
||||
renewApplicationToken: vi.fn().mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
applicationAccessToken: {
|
||||
token: 'mock-renewed-access-token',
|
||||
expiresAt: '',
|
||||
},
|
||||
applicationRefreshToken: {
|
||||
token: 'mock-renewed-refresh-token',
|
||||
expiresAt: '',
|
||||
},
|
||||
},
|
||||
}),
|
||||
syncApplication: vi.fn().mockResolvedValue({ success: true, data: true }),
|
||||
uploadFile: vi.fn().mockResolvedValue({ success: true, data: true }),
|
||||
};
|
||||
@@ -15,8 +33,10 @@ const mockApiService = {
|
||||
vi.mock('@/cli/utilities/api/api-service', () => ({
|
||||
ApiService: class {
|
||||
validateAuth = mockApiService.validateAuth;
|
||||
checkApplicationExist = mockApiService.checkApplicationExist;
|
||||
findOneApplication = mockApiService.findOneApplication;
|
||||
createApplication = mockApiService.createApplication;
|
||||
generateApplicationToken = mockApiService.generateApplicationToken;
|
||||
renewApplicationToken = mockApiService.renewApplicationToken;
|
||||
syncApplication = mockApiService.syncApplication;
|
||||
uploadFile = mockApiService.uploadFile;
|
||||
},
|
||||
@@ -28,6 +48,6 @@ vi.mock('@/cli/utilities/file/file-uploader', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/cli/utilities/dev/dev-ui', () => ({
|
||||
vi.mock('@/cli/utilities/dev/ui/components/dev-ui', () => ({
|
||||
renderDevUI: vi.fn().mockResolvedValue({ unmount: vi.fn() }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user