[FRONT COMPONENTS] Declare command menu items in front components (#18047)

## Description

- Adds support for declaring command menu items directly within
`defineFrontComponent` via an optional command config property
- Introduces a new `CommandMenuItemManifest` type in twenty-shared and
wires it through the manifest build pipeline

## Example Of usage

```tsx
import { defineFrontComponent } from "twenty-sdk";

const TestAction = () => {
  return <div>Test Action</div>;
};

export default defineFrontComponent({
  universalIdentifier: "6c289461-0007-4a62-a99f-69e5c11a4ce7",
  name: "test-action",
  description: "Test Action",
  component: TestAction,
  command: {
    universalIdentifier: "c07df864-495f-46f3-9f5b-9d3ce2589e9b",
    label: "Run My Action",
    icon: "IconBolt",
    isPinned: false,
  },
});

```

## Video QA


https://github.com/user-attachments/assets/f910fc6a-44a9-45d1-87c5-f0ce64bb3878
This commit is contained in:
Raphaël Bosi
2026-02-19 15:23:46 +01:00
committed by GitHub
parent b3d8f8813f
commit bd03073b6d
10 changed files with 104 additions and 4 deletions
@@ -16,6 +16,7 @@ export const APPLICATION_MANIFEST_METADATA_NAMES = [
'pageLayout',
'pageLayoutTab',
'pageLayoutWidget',
'commandMenuItem',
] as const satisfies AllMetadataName[];
export type ApplicationManifestMetadataName =
@@ -1,6 +1,7 @@
import { type Manifest } from 'twenty-shared/application';
import { type ApplicationManifestMetadataName } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
import { fromCommandMenuItemManifestToUniversalFlatCommandMenuItem } from 'src/engine/core-modules/application/utils/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util';
import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/utils/from-field-manifest-to-universal-flat-field-metadata.util';
import { fromFrontComponentManifestToUniversalFlatFrontComponent } from 'src/engine/core-modules/application/utils/from-front-component-manifest-to-universal-flat-front-component.util';
import { fromLogicFunctionManifestToUniversalFlatLogicFunction } from 'src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util';
@@ -117,6 +118,25 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
if (frontComponentManifest.command) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'commandMenuItem',
universalFlatEntity:
fromCommandMenuItemManifestToUniversalFlatCommandMenuItem({
commandMenuItemManifest: {
...frontComponentManifest.command,
frontComponentUniversalIdentifier:
frontComponentManifest.universalIdentifier,
},
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
}
}
for (const roleManifest of manifest.roles) {
@@ -0,0 +1,41 @@
import { type CommandMenuItemManifest } from 'twenty-shared/application';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
import { type UniversalFlatCommandMenuItem } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-command-menu-item.type';
const AVAILABILITY_TYPE_MAP: Record<
NonNullable<CommandMenuItemManifest['availabilityType']>,
CommandMenuItemAvailabilityType
> = {
GLOBAL: CommandMenuItemAvailabilityType.GLOBAL,
SINGLE_RECORD: CommandMenuItemAvailabilityType.SINGLE_RECORD,
BULK_RECORDS: CommandMenuItemAvailabilityType.BULK_RECORDS,
};
export const fromCommandMenuItemManifestToUniversalFlatCommandMenuItem = ({
commandMenuItemManifest,
applicationUniversalIdentifier,
now,
}: {
commandMenuItemManifest: CommandMenuItemManifest;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatCommandMenuItem => {
return {
universalIdentifier: commandMenuItemManifest.universalIdentifier,
applicationUniversalIdentifier,
label: commandMenuItemManifest.label,
icon: commandMenuItemManifest.icon ?? null,
isPinned: commandMenuItemManifest.isPinned ?? false,
availabilityType: commandMenuItemManifest.availabilityType
? AVAILABILITY_TYPE_MAP[commandMenuItemManifest.availabilityType]
: CommandMenuItemAvailabilityType.GLOBAL,
frontComponentUniversalIdentifier:
commandMenuItemManifest.frontComponentUniversalIdentifier,
availabilityObjectMetadataUniversalIdentifier:
commandMenuItemManifest.availabilityObjectUniversalIdentifier ?? null,
workflowVersionId: null,
createdAt: now,
updatedAt: now,
};
};