Add sync front component (#17748)

## Context
Allow twenty apps to sync front components
This commit is contained in:
Weiko
2026-02-09 00:00:23 +01:00
committed by GitHub
parent 40d7e740ef
commit 9aa63f7ddc
33 changed files with 643 additions and 97 deletions
@@ -11,6 +11,10 @@ type FrontComponentSeed = {
workspaceId: string;
universalIdentifier: string;
applicationId: string;
sourceComponentPath: string;
builtComponentPath: string;
componentName: string;
builtComponentChecksum: string;
};
type CommandMenuItemSeed = {
@@ -48,6 +52,10 @@ export const getFrontComponentAndCommandMenuItemDataSeeds = (
workspaceId,
universalIdentifier: frontComponentId,
applicationId,
sourceComponentPath: 'src/front-components/demo-app.tsx',
builtComponentPath: 'src/front-components/demo-app.mjs',
componentName: 'DemoApp',
builtComponentChecksum: '1234567890',
},
];
@@ -28,6 +28,10 @@ export const seedFrontComponentsAndCommandMenuItems = async ({
'workspaceId',
'universalIdentifier',
'applicationId',
'sourceComponentPath',
'builtComponentPath',
'componentName',
'builtComponentChecksum',
])
.values(
frontComponents.map((row) => ({
@@ -36,6 +40,10 @@ export const seedFrontComponentsAndCommandMenuItems = async ({
workspaceId: row.workspaceId,
universalIdentifier: row.universalIdentifier,
applicationId: row.applicationId,
sourceComponentPath: row.sourceComponentPath,
builtComponentPath: row.builtComponentPath,
componentName: row.componentName,
builtComponentChecksum: row.builtComponentChecksum,
})),
)
.orIgnore()
@@ -1,8 +1,16 @@
import { Injectable } from '@nestjs/common';
import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
import {
FrontComponentException,
FrontComponentExceptionCode,
} from 'src/engine/metadata-modules/front-component/front-component.exception';
import { FlatCreateFrontComponentAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/front-component/types/workspace-migration-front-component-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
@@ -14,7 +22,7 @@ export class CreateFrontComponentActionHandlerService extends WorkspaceMigration
'create',
'frontComponent',
) {
constructor() {
constructor(private readonly fileStorageService: FileStorageService) {
super();
}
@@ -27,8 +35,18 @@ export class CreateFrontComponentActionHandlerService extends WorkspaceMigration
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatCreateFrontComponentAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const { flatEntity } = flatAction;
const { flatAction, queryRunner, workspaceId, flatApplication } = context;
const { flatEntity: frontComponent } = flatAction;
const applicationUniversalIdentifier = flatApplication.universalIdentifier;
if (isDefined(frontComponent.builtComponentChecksum)) {
await this.verifySourceAndBuiltFilesExist({
workspaceId,
applicationUniversalIdentifier,
builtComponentPath: frontComponent.builtComponentPath,
});
}
const frontComponentRepository =
queryRunner.manager.getRepository<FrontComponentEntity>(
@@ -36,11 +54,35 @@ export class CreateFrontComponentActionHandlerService extends WorkspaceMigration
);
await frontComponentRepository.insert({
...flatEntity,
...frontComponent,
workspaceId,
});
}
private async verifySourceAndBuiltFilesExist({
workspaceId,
applicationUniversalIdentifier,
builtComponentPath,
}: {
workspaceId: string;
applicationUniversalIdentifier: string;
builtComponentPath: string;
}): Promise<void> {
const builtExists = await this.fileStorageService.checkFileExists_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltFrontComponent,
resourcePath: builtComponentPath,
});
if (!builtExists) {
throw new FrontComponentException(
`Front component built file missing before create (built: ${builtExists})`,
FrontComponentExceptionCode.FRONT_COMPONENT_CREATE_FAILED,
);
}
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatCreateFrontComponentAction>,
): Promise<void> {