feat(sdk): add defineCommandMenuItem (#20256)
## Summary - Add `defineCommandMenuItem` and `definePageLayoutWidget` as standalone SDK defines, mirroring the existing `definePageLayoutTab` pattern. Both entities can still be declared nested inside their parent (`defineFrontComponent.command` / `definePageLayout.tabs[].widgets[]`). - Add `CommandMenuItem` and `PageLayoutWidget` to the `SyncableEntity` enum and the dev-mode UI labels. - Wire the SDK manifest-build to extract the two new defines into top-level `commandMenuItems` / `pageLayoutWidgets` arrays on the manifest, and the server aggregator to consume them through the existing flat-entity converters. - On the server, expose `Application.commandMenuItems` (relation + DTO + service hydration in `findOneApplication`). - On the front, list command menu items in the application content tab and add a dedicated detail page with a settings tab, mirroring how `frontComponents` are surfaced. - Add `twenty add` templates and Vitest unit tests for both new defines. - Document the standalone-vs-nested pattern in `packages/twenty-sdk/README.md`. ### Why Until now, command menu items could only be declared as the nested `command:` field on `defineFrontComponent` — there was no way to register a command menu item from a separate file or from another package. The `SyncableEntity` enum had 12 values, while the server already synced 18 (including `commandMenuItem` and `pageLayoutWidget`). The same gap existed for `pageLayoutWidget`, which had no top-level define despite being synced server-side. This PR closes both gaps and aligns the SDK surface with what the server actually accepts. The standalone defines coexist with the nested form — pick one per entity, never both with the same `universalIdentifier` (the manifest aggregator will throw on duplicates). The README now documents this. ## Test plan - [x] `npx nx typecheck twenty-sdk` / `twenty-server` / `twenty-front` - [x] `npx nx lint:diff-with-main twenty-front` / `twenty-server` - [x] `npx nx lint twenty-sdk` / `twenty-shared` - [x] New unit tests: `define-command-menu-item.spec.ts`, `define-page-layout-widget.spec.ts` - [x] Existing manifest extract config tests still pass - [ ] Codegen `npx nx run twenty-front:graphql:generate --configuration=metadata` should be re-run after merge — the generated `graphql.ts` was patched manually to include `commandMenuItems` on `Application` and the `FindOneApplication` document. - [ ] Smoke test: scaffold an app with `twenty add` for both new entity types, run `twenty dev`, confirm the dev UI shows them in the sync list and the settings page surfaces command menu items in the content tab. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: martmull <martmull@hotmail.fr> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@@ -11,4 +11,5 @@ export enum SyncableEntity {
|
||||
NavigationMenuItem = 'navigationMenuItem',
|
||||
PageLayout = 'pageLayout',
|
||||
PageLayoutTab = 'pageLayoutTab',
|
||||
CommandMenuItem = 'commandMenuItem',
|
||||
}
|
||||
|
||||
@@ -15,11 +15,6 @@ export type CommandMenuItemManifest = SyncableEntityOptions & {
|
||||
conditionalAvailabilityExpression?: string;
|
||||
};
|
||||
|
||||
export type FrontComponentCommandManifest = Omit<
|
||||
CommandMenuItemManifest,
|
||||
'frontComponentUniversalIdentifier'
|
||||
>;
|
||||
|
||||
export type FrontComponentManifest = {
|
||||
universalIdentifier: string;
|
||||
name?: string;
|
||||
@@ -30,5 +25,4 @@ export type FrontComponentManifest = {
|
||||
componentName: string;
|
||||
isHeadless?: boolean;
|
||||
usesSdkClient?: boolean;
|
||||
command?: FrontComponentCommandManifest;
|
||||
};
|
||||
|
||||
@@ -31,7 +31,6 @@ export type {
|
||||
} from './fieldManifestType';
|
||||
export type {
|
||||
CommandMenuItemManifest,
|
||||
FrontComponentCommandManifest,
|
||||
FrontComponentManifest,
|
||||
} from './frontComponentManifestType';
|
||||
export type {
|
||||
|
||||
@@ -3,7 +3,10 @@ import { type ApplicationManifest } from './applicationType';
|
||||
import { type AssetManifest } from './assetManifestType';
|
||||
import { type ConnectionProviderManifest } from './connectionProviderManifestType';
|
||||
import { type FieldManifest } from './fieldManifestType';
|
||||
import { type FrontComponentManifest } from './frontComponentManifestType';
|
||||
import {
|
||||
type CommandMenuItemManifest,
|
||||
type FrontComponentManifest,
|
||||
} from './frontComponentManifestType';
|
||||
import { type LogicFunctionManifest } from './logicFunctionManifestType';
|
||||
import { type NavigationMenuItemManifest } from './navigationMenuItemManifestType';
|
||||
import { type ObjectManifest } from './objectManifestType';
|
||||
@@ -30,4 +33,5 @@ export type Manifest = {
|
||||
navigationMenuItems: NavigationMenuItemManifest[];
|
||||
pageLayouts: PageLayoutManifest[];
|
||||
pageLayoutTabs: PageLayoutTabManifest[];
|
||||
commandMenuItems: CommandMenuItemManifest[];
|
||||
};
|
||||
|
||||
@@ -44,6 +44,7 @@ export enum SettingsPath {
|
||||
ApplicationDetail = 'applications/:applicationId',
|
||||
ApplicationLogicFunctionDetail = 'applications/:applicationId/logicFunctions/:logicFunctionId',
|
||||
ApplicationFrontComponentDetail = 'applications/:applicationId/frontComponents/:frontComponentId',
|
||||
ApplicationCommandMenuItemDetail = 'applications/:applicationId/commandMenuItems/:commandMenuItemId',
|
||||
ApplicationViewDetail = 'applications/:applicationId/views/:viewUniversalIdentifier',
|
||||
ApplicationPageLayoutDetail = 'applications/:applicationId/pageLayouts/:pageLayoutUniversalIdentifier',
|
||||
AvailableApplicationDetail = 'applications/available/:availableApplicationId',
|
||||
|
||||
Reference in New Issue
Block a user