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:
+25
-4
@@ -52,10 +52,7 @@ export class ApplicationSyncService {
|
||||
hasSchemaMetadataChanged: boolean;
|
||||
}> {
|
||||
const ownerFlatApplication: FlatApplication = dryRun
|
||||
? await this.applicationService.findOneApplicationOrThrow({
|
||||
universalIdentifier: manifest.application.universalIdentifier,
|
||||
workspaceId,
|
||||
})
|
||||
? await this.findInstalledApplicationOrThrow({ workspaceId, manifest })
|
||||
: await this.syncApplication({
|
||||
workspaceId,
|
||||
manifest,
|
||||
@@ -77,6 +74,30 @@ export class ApplicationSyncService {
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
private async findInstalledApplicationOrThrow({
|
||||
workspaceId,
|
||||
manifest,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
manifest: Manifest;
|
||||
}): Promise<ApplicationEntity> {
|
||||
const application = await this.applicationService.findByUniversalIdentifier(
|
||||
{
|
||||
universalIdentifier: manifest.application.universalIdentifier,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (!application) {
|
||||
throw new ApplicationException(
|
||||
`Application "${manifest.application.universalIdentifier}" is not installed in workspace "${workspaceId}". Install it first.`,
|
||||
ApplicationExceptionCode.APP_NOT_INSTALLED,
|
||||
);
|
||||
}
|
||||
|
||||
return application;
|
||||
}
|
||||
|
||||
// Registers the application + only the pre-install logic function in
|
||||
// workspace metadata so the pre-install hook can resolve and execute it
|
||||
// before the main synchronizeFromManifest runs the full migrations.
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ export const fromLogicFunctionManifestToUniversalFlatLogicFunction = ({
|
||||
workflowActionTriggerSettings:
|
||||
logicFunctionManifest.workflowActionTriggerSettings ?? null,
|
||||
isBuildUpToDate: true,
|
||||
executionMode: LogicFunctionExecutionMode.PREBUILT,
|
||||
executionMode: LogicFunctionExecutionMode.LIVE,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
|
||||
import { type MetadataUniversalFlatEntityPropertiesToCompare } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-properties-to-compare.type';
|
||||
|
||||
export type UniversalFlatEntityDiff<T extends AllMetadataName> = {
|
||||
[K in MetadataUniversalFlatEntityPropertiesToCompare<T>]?: {
|
||||
before: MetadataUniversalFlatEntity<T>[K];
|
||||
after: MetadataUniversalFlatEntity<T>[K];
|
||||
};
|
||||
};
|
||||
+23
-2
@@ -18,6 +18,7 @@ import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-e
|
||||
import { WorkspaceMigrationBuilderAdditionalCacheDataMaps } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-builder-additional-cache-data-maps.type';
|
||||
import { AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
|
||||
import { MetadataUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-maps.type';
|
||||
import { UniversalFlatEntityDiff } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-diff.type';
|
||||
import { UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
|
||||
import { addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
|
||||
import { deleteUniversalFlatEntityForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-foreign-key-aggregators.util';
|
||||
@@ -164,7 +165,11 @@ export abstract class WorkspaceEntityMigrationBuilderService<
|
||||
actionsResult.delete.push(
|
||||
...(Array.isArray(validationResult.action)
|
||||
? validationResult.action
|
||||
: [validationResult.action]),
|
||||
: [validationResult.action]
|
||||
).map((action) => ({
|
||||
...action,
|
||||
flatEntity: universalFlatEntityToDelete,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -221,6 +226,17 @@ export abstract class WorkspaceEntityMigrationBuilderService<
|
||||
...flatEntityUpdate.update,
|
||||
};
|
||||
|
||||
const diff = Object.fromEntries(
|
||||
Object.entries(flatEntityUpdate.update).map(([key, after]) => [
|
||||
key,
|
||||
{
|
||||
before:
|
||||
existingFlatEntity[key as keyof MetadataUniversalFlatEntity<T>],
|
||||
after,
|
||||
},
|
||||
]),
|
||||
) as UniversalFlatEntityDiff<T>;
|
||||
|
||||
replaceUniversalFlatEntityInUniversalFlatEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
universalFlatEntity: updatedFlatEntity,
|
||||
@@ -232,7 +248,12 @@ export abstract class WorkspaceEntityMigrationBuilderService<
|
||||
actionsResult.update.push(
|
||||
...(Array.isArray(validationResult.action)
|
||||
? validationResult.action
|
||||
: [validationResult.action]),
|
||||
: [validationResult.action]
|
||||
).map((action) => ({
|
||||
...action,
|
||||
flatEntity: updatedFlatEntity,
|
||||
diff,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
|
||||
import { type WORKSPACE_MIGRATION_ACTION_TYPE } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/constants/workspace-migration-action-type.constant';
|
||||
|
||||
export type BaseUniversalDeleteWorkspaceMigrationAction<
|
||||
@@ -8,4 +9,5 @@ export type BaseUniversalDeleteWorkspaceMigrationAction<
|
||||
universalIdentifier: string;
|
||||
type: typeof WORKSPACE_MIGRATION_ACTION_TYPE.delete;
|
||||
metadataName: T;
|
||||
flatEntity?: MetadataUniversalFlatEntity<T>;
|
||||
};
|
||||
|
||||
+4
@@ -1,5 +1,7 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
|
||||
import { type UniversalFlatEntityDiff } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-diff.type';
|
||||
import { type UniversalFlatEntityUpdate } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-update.type';
|
||||
import { type WORKSPACE_MIGRATION_ACTION_TYPE } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/constants/workspace-migration-action-type.constant';
|
||||
|
||||
@@ -10,4 +12,6 @@ export type BaseUniversalUpdateWorkspaceMigrationAction<
|
||||
metadataName: T;
|
||||
universalIdentifier: string;
|
||||
update: UniversalFlatEntityUpdate<T>;
|
||||
flatEntity?: MetadataUniversalFlatEntity<T>;
|
||||
diff?: UniversalFlatEntityDiff<T>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user