feat(app-dev): sync error hints, flatEntity labels, dev-mode summary UI, and docs (#21252)
Split out of #21240 — all remaining app-dev improvements. Stacked on #21251 (review/merge that first). - Actionable recovery hints on failed syncs; unified diff renderer; `--dry-run` guard. - Return `flatEntity` on update/delete sync actions and unify the diff label. - Summarize the dev-mode entity list unless `--verbose`. - Docs: syncing & recovery guide + dry-run + open-an-issue prompt. - Live execution mode for synced logic functions; clearer manifest warnings. <img width="1018" height="700" alt="image" src="https://github.com/user-attachments/assets/5e9ce19e-0f1d-4f99-8524-4e118bde932b" />
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
type SuccessfulApiResponse<T = unknown> = {
|
||||
type SuccessfulApiResponse<TData = unknown> = {
|
||||
success: true;
|
||||
data: T;
|
||||
data: TData;
|
||||
message?: string;
|
||||
};
|
||||
type FailingApiResponse = {
|
||||
type FailingApiResponse<TError = unknown> = {
|
||||
success: false;
|
||||
error?: unknown;
|
||||
error?: TError;
|
||||
message?: string;
|
||||
};
|
||||
export type ApiResponse<T = unknown> =
|
||||
| SuccessfulApiResponse<T>
|
||||
| FailingApiResponse;
|
||||
export type ApiResponse<TData = unknown, TError = unknown> =
|
||||
| SuccessfulApiResponse<TData>
|
||||
| FailingApiResponse<TError>;
|
||||
|
||||
@@ -5,7 +5,10 @@ import { FileApi } from '@/cli/utilities/api/file-api';
|
||||
import { LogicFunctionApi } from '@/cli/utilities/api/logic-function-api';
|
||||
import { SchemaApi } from '@/cli/utilities/api/schema-api';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { type SyncAction } from 'twenty-shared/metadata';
|
||||
import {
|
||||
type MetadataValidationErrorResponse,
|
||||
type SyncAction,
|
||||
} from 'twenty-shared/metadata';
|
||||
|
||||
type ApiServiceOptions = {
|
||||
disableInterceptors?: boolean;
|
||||
@@ -77,10 +80,13 @@ export class ApiService {
|
||||
manifest: Manifest,
|
||||
options?: { dryRun?: boolean },
|
||||
): Promise<
|
||||
ApiResponse<{
|
||||
applicationUniversalIdentifier: string;
|
||||
actions: SyncAction[];
|
||||
}>
|
||||
ApiResponse<
|
||||
{
|
||||
applicationUniversalIdentifier: string;
|
||||
actions: SyncAction[];
|
||||
},
|
||||
MetadataValidationErrorResponse
|
||||
>
|
||||
> {
|
||||
return this.applicationApi.syncApplication(manifest, options);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { type ApiResponse } from '@/cli/utilities/api/api-response-type';
|
||||
import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
|
||||
import { serializeError } from '@/cli/utilities/error/serialize-error';
|
||||
import axios, { type AxiosInstance } from 'axios';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { type SyncAction } from 'twenty-shared/metadata';
|
||||
import {
|
||||
type MetadataValidationErrorResponse,
|
||||
type SyncAction,
|
||||
} from 'twenty-shared/metadata';
|
||||
|
||||
export class ApplicationApi {
|
||||
constructor(private readonly client: AxiosInstance) {}
|
||||
@@ -256,10 +260,13 @@ export class ApplicationApi {
|
||||
manifest: Manifest,
|
||||
options?: { dryRun?: boolean },
|
||||
): Promise<
|
||||
ApiResponse<{
|
||||
applicationUniversalIdentifier: string;
|
||||
actions: SyncAction[];
|
||||
}>
|
||||
ApiResponse<
|
||||
{
|
||||
applicationUniversalIdentifier: string;
|
||||
actions: SyncAction[];
|
||||
},
|
||||
MetadataValidationErrorResponse
|
||||
>
|
||||
> {
|
||||
try {
|
||||
const mutation = `
|
||||
@@ -273,7 +280,7 @@ export class ApplicationApi {
|
||||
|
||||
const variables = { manifest, dryRun: options?.dryRun ?? false };
|
||||
|
||||
const response: AxiosResponse = await this.client.post(
|
||||
const response = await this.client.post(
|
||||
'/metadata',
|
||||
{
|
||||
query: mutation,
|
||||
@@ -290,7 +297,8 @@ export class ApplicationApi {
|
||||
if (response.data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
error: response.data.errors[0],
|
||||
error: response.data.errors[0]?.extensions,
|
||||
message: response.data.errors[0]?.message,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -300,27 +308,9 @@ export class ApplicationApi {
|
||||
message: `Successfully synced application: ${manifest.application.displayName}`,
|
||||
};
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response) {
|
||||
const graphqlErrors = error.response.data?.errors;
|
||||
|
||||
if (Array.isArray(graphqlErrors) && graphqlErrors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: graphqlErrors[0]?.message || error.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error.response.data?.message ||
|
||||
`HTTP ${error.response.status}: ${error.message}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : error,
|
||||
message: serializeError(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -337,7 +327,7 @@ export class ApplicationApi {
|
||||
|
||||
const variables = { universalIdentifier };
|
||||
|
||||
const response: AxiosResponse = await this.client.post(
|
||||
const response = await this.client.post(
|
||||
'/metadata',
|
||||
{
|
||||
query: mutation,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { type ApiResponse } from '@/cli/utilities/api/api-response-type';
|
||||
import { serializeError } from '@/cli/utilities/error/serialize-error';
|
||||
import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { type MetadataValidationErrorResponse } from 'twenty-shared/metadata';
|
||||
import { type FileFolder } from 'twenty-shared/types';
|
||||
import { pascalCase } from 'twenty-shared/utils';
|
||||
|
||||
@@ -136,7 +138,7 @@ export class FileApi {
|
||||
universalIdentifier,
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
}): Promise<ApiResponse<boolean>> {
|
||||
}): Promise<ApiResponse<boolean, MetadataValidationErrorResponse>> {
|
||||
try {
|
||||
const mutation = `
|
||||
mutation InstallApplication($universalIdentifier: String!) {
|
||||
@@ -163,7 +165,9 @@ export class FileApi {
|
||||
if (response.data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
error: response.data.errors[0] || 'Failed to install application',
|
||||
error: response.data.errors[0]?.extensions,
|
||||
message:
|
||||
response.data.errors[0]?.message || 'Failed to install application',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -172,16 +176,9 @@ export class FileApi {
|
||||
data: response.data.data.installApplication,
|
||||
};
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.response.data?.errors?.[0]?.message || error.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error,
|
||||
message: serializeError(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user