From 22a203680e54865253812ad198ec0adf0682bbf0 Mon Sep 17 00:00:00 2001 From: martmull Date: Mon, 9 Mar 2026 15:51:46 +0100 Subject: [PATCH] Fix wrong type usage (#18499) fix wrong type usage + add tests --- packages/create-twenty-app/package.json | 2 +- packages/twenty-sdk/package.json | 2 +- .../__tests__/get-agent-base-file.spec.ts | 53 ++++++++ .../__tests__/get-field-base-file.spec.ts | 100 +++++++++++++++ ...get-navigation-menu-item-base-file.spec.ts | 60 +++++++++ .../get-page-layout-base-file.spec.ts | 60 +++++++++ .../__tests__/get-skill-base-file.spec.ts | 64 ++++++++++ .../__tests__/get-view-base-file.spec.ts | 115 ++++++++++++++++++ .../utilities/entity/entity-field-template.ts | 2 +- 9 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-agent-base-file.spec.ts create mode 100644 packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-field-base-file.spec.ts create mode 100644 packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-navigation-menu-item-base-file.spec.ts create mode 100644 packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-page-layout-base-file.spec.ts create mode 100644 packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-skill-base-file.spec.ts create mode 100644 packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-view-base-file.spec.ts diff --git a/packages/create-twenty-app/package.json b/packages/create-twenty-app/package.json index fa99d26979..632aa34351 100644 --- a/packages/create-twenty-app/package.json +++ b/packages/create-twenty-app/package.json @@ -1,6 +1,6 @@ { "name": "create-twenty-app", - "version": "0.6.4", + "version": "0.7.0-canary.0", "description": "Command-line interface to create Twenty application", "main": "dist/cli.cjs", "bin": "dist/cli.cjs", diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index f6f87b61c4..2e99372326 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -1,6 +1,6 @@ { "name": "twenty-sdk", - "version": "0.6.4", + "version": "0.7.0-canary.0", "main": "dist/index.cjs", "module": "dist/index.mjs", "types": "dist/sdk/index.d.ts", diff --git a/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-agent-base-file.spec.ts b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-agent-base-file.spec.ts new file mode 100644 index 0000000000..b6f2ad6608 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-agent-base-file.spec.ts @@ -0,0 +1,53 @@ +import { getAgentBaseFile } from '@/cli/utilities/entity/entity-agent-template'; + +describe('getAgentBaseFile', () => { + it('should render proper file using defineAgent', () => { + const result = getAgentBaseFile({ + name: 'my-agent', + universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4', + }); + + expect(result).toContain("import { defineAgent } from 'twenty-sdk'"); + expect(result).toContain('export default defineAgent({'); + expect(result).toContain( + 'universalIdentifier: MY_AGENT_AGENT_UNIVERSAL_IDENTIFIER', + ); + expect(result).toContain("'71e45a58-41da-4ae4-8b73-a543c0a9d3d4'"); + expect(result).toContain("name: 'my-agent'"); + expect(result).toContain("label: 'my-agent'"); + expect(result).toContain("description: 'Add a description for your agent'"); + expect(result).toContain("prompt: 'Add the agent system prompt here'"); + }); + + it('should generate unique UUID when not provided', () => { + const result = getAgentBaseFile({ + name: 'auto-uuid-agent', + }); + + expect(result).toMatch( + /'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/, + ); + }); + + it('should export universal identifier constant with correct naming', () => { + const result = getAgentBaseFile({ + name: 'data-sync', + }); + + expect(result).toContain( + 'export const DATA_SYNC_AGENT_UNIVERSAL_IDENTIFIER', + ); + expect(result).toContain( + 'universalIdentifier: DATA_SYNC_AGENT_UNIVERSAL_IDENTIFIER', + ); + }); + + it('should use kebab-case for name in template', () => { + const result = getAgentBaseFile({ + name: 'my-awesome-agent', + }); + + expect(result).toContain("name: 'my-awesome-agent'"); + expect(result).toContain("label: 'my-awesome-agent'"); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-field-base-file.spec.ts b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-field-base-file.spec.ts new file mode 100644 index 0000000000..ed4a2c5f8c --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-field-base-file.spec.ts @@ -0,0 +1,100 @@ +import { getFieldBaseFile } from '@/cli/utilities/entity/entity-field-template'; + +describe('getFieldBaseFile', () => { + it('should render proper file using defineField', () => { + const result = getFieldBaseFile({ + data: { + name: 'email', + label: 'Email', + type: 'TEXT' as any, + objectUniversalIdentifier: 'abc-123', + }, + name: 'email', + }); + + expect(result).toContain( + "import { defineField, FieldType } from 'twenty-sdk'", + ); + expect(result).toContain('export default defineField({'); + expect(result).toContain("name: 'email'"); + expect(result).toContain("label: 'Email'"); + expect(result).toContain('type: FieldType.TEXT'); + expect(result).toContain("objectUniversalIdentifier: 'abc-123'"); + }); + + it('should include description when provided', () => { + const result = getFieldBaseFile({ + data: { + name: 'phone', + label: 'Phone', + type: 'PHONES' as any, + objectUniversalIdentifier: 'obj-456', + description: 'Contact phone number', + }, + name: 'phone', + }); + + expect(result).toContain("description: 'Contact phone number'"); + }); + + it('should omit description line when not provided', () => { + const result = getFieldBaseFile({ + data: { + name: 'phone', + label: 'Phone', + type: 'PHONES' as any, + objectUniversalIdentifier: 'obj-456', + }, + name: 'phone', + }); + + expect(result).not.toContain('description:'); + }); + + it('should generate a valid UUID for universalIdentifier', () => { + const result = getFieldBaseFile({ + data: { + name: 'status', + label: 'Status', + type: 'SELECT' as any, + objectUniversalIdentifier: 'obj-789', + }, + name: 'status', + }); + + expect(result).toMatch( + /universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/, + ); + }); + + it('should generate unique UUIDs for each call', () => { + const result1 = getFieldBaseFile({ + data: { + name: 'field1', + label: 'Field 1', + type: 'TEXT' as any, + objectUniversalIdentifier: 'obj-1', + }, + name: 'field1', + }); + + const result2 = getFieldBaseFile({ + data: { + name: 'field2', + label: 'Field 2', + type: 'TEXT' as any, + objectUniversalIdentifier: 'obj-2', + }, + name: 'field2', + }); + + const uuidRegex = + /universalIdentifier: '([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/; + const uuid1 = result1.match(uuidRegex)?.[1]; + const uuid2 = result2.match(uuidRegex)?.[1]; + + expect(uuid1).toBeDefined(); + expect(uuid2).toBeDefined(); + expect(uuid1).not.toBe(uuid2); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-navigation-menu-item-base-file.spec.ts b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-navigation-menu-item-base-file.spec.ts new file mode 100644 index 0000000000..82323b6a55 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-navigation-menu-item-base-file.spec.ts @@ -0,0 +1,60 @@ +import { getNavigationMenuItemBaseFile } from '@/cli/utilities/entity/entity-navigation-menu-item-template'; + +describe('getNavigationMenuItemBaseFile', () => { + it('should render proper file using defineNavigationMenuItem', () => { + const result = getNavigationMenuItemBaseFile({ + name: 'my-nav-item', + universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4', + }); + + expect(result).toContain( + "import { defineNavigationMenuItem } from 'twenty-sdk'", + ); + expect(result).toContain('export default defineNavigationMenuItem({'); + expect(result).toContain( + "universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4'", + ); + expect(result).toContain("name: 'my-nav-item'"); + expect(result).toContain("icon: 'IconList'"); + expect(result).toContain('position: 0'); + }); + + it('should include viewUniversalIdentifier when provided', () => { + const result = getNavigationMenuItemBaseFile({ + name: 'linked-item', + viewUniversalIdentifier: 'view-uuid-123', + }); + + expect(result).toContain("viewUniversalIdentifier: 'view-uuid-123'"); + expect(result).not.toContain('// Link to a view:'); + }); + + it('should include commented link options when viewUniversalIdentifier is not provided', () => { + const result = getNavigationMenuItemBaseFile({ + name: 'unlinked-item', + }); + + expect(result).toContain('// Link to a view:'); + expect(result).toContain('// viewUniversalIdentifier:'); + expect(result).toContain('// targetObjectUniversalIdentifier:'); + expect(result).toContain('// link:'); + }); + + it('should generate unique UUID when not provided', () => { + const result = getNavigationMenuItemBaseFile({ + name: 'auto-uuid-nav', + }); + + expect(result).toMatch( + /universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/, + ); + }); + + it('should use kebab-case for name', () => { + const result = getNavigationMenuItemBaseFile({ + name: 'dashboard overview', + }); + + expect(result).toContain("name: 'dashboard-overview'"); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-page-layout-base-file.spec.ts b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-page-layout-base-file.spec.ts new file mode 100644 index 0000000000..8d56553c85 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-page-layout-base-file.spec.ts @@ -0,0 +1,60 @@ +import { getPageLayoutBaseFile } from '@/cli/utilities/entity/entity-page-layout-template'; + +describe('getPageLayoutBaseFile', () => { + it('should render proper file using definePageLayout', () => { + const result = getPageLayoutBaseFile({ + name: 'my-layout', + }); + + expect(result).toContain("import { definePageLayout } from 'twenty-sdk'"); + expect(result).toContain('export default definePageLayout({'); + expect(result).toContain("name: 'my-layout'"); + expect(result).toContain("title: 'Overview'"); + expect(result).toContain('widgets: []'); + expect(result).toContain('tabs: ['); + }); + + it('should generate valid UUIDs for layout and tab', () => { + const result = getPageLayoutBaseFile({ + name: 'test-layout', + }); + + const uuidRegex = + /universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/g; + const matches = result.match(uuidRegex); + + // Should have two UUIDs: one for the layout and one for the tab + expect(matches).toHaveLength(2); + }); + + it('should generate unique UUIDs for layout and tab', () => { + const result = getPageLayoutBaseFile({ + name: 'unique-layout', + }); + + const uuidRegex = + /universalIdentifier: '([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/g; + const uuids: string[] = []; + let match; + + while ((match = uuidRegex.exec(result)) !== null) { + uuids.push(match[1]); + } + + expect(uuids[0]).not.toBe(uuids[1]); + }); + + it('should generate unique UUIDs across calls', () => { + const result1 = getPageLayoutBaseFile({ name: 'layout-1' }); + const result2 = getPageLayoutBaseFile({ name: 'layout-2' }); + + const uuidRegex = + /universalIdentifier: '([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'/; + const uuid1 = result1.match(uuidRegex)?.[1]; + const uuid2 = result2.match(uuidRegex)?.[1]; + + expect(uuid1).toBeDefined(); + expect(uuid2).toBeDefined(); + expect(uuid1).not.toBe(uuid2); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-skill-base-file.spec.ts b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-skill-base-file.spec.ts new file mode 100644 index 0000000000..07e0c896dc --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-skill-base-file.spec.ts @@ -0,0 +1,64 @@ +import { getSkillBaseFile } from '@/cli/utilities/entity/entity-skill-template'; + +describe('getSkillBaseFile', () => { + it('should render proper file using defineSkill', () => { + const result = getSkillBaseFile({ + name: 'my-skill', + universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4', + }); + + expect(result).toContain("import { defineSkill } from 'twenty-sdk'"); + expect(result).toContain('export default defineSkill({'); + expect(result).toContain( + 'universalIdentifier: MY_SKILL_SKILL_UNIVERSAL_IDENTIFIER', + ); + expect(result).toContain("'71e45a58-41da-4ae4-8b73-a543c0a9d3d4'"); + expect(result).toContain("name: 'my-skill'"); + expect(result).toContain("label: 'my-skill'"); + expect(result).toContain("description: 'Add a description for your skill'"); + expect(result).toContain("content: 'Add the skill content here'"); + }); + + it('should generate unique UUID when not provided', () => { + const result = getSkillBaseFile({ + name: 'auto-uuid-skill', + }); + + expect(result).toMatch( + /'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/, + ); + }); + + it('should export universal identifier constant with correct naming', () => { + const result = getSkillBaseFile({ + name: 'data-export', + }); + + expect(result).toContain( + 'export const DATA_EXPORT_SKILL_UNIVERSAL_IDENTIFIER', + ); + expect(result).toContain( + 'universalIdentifier: DATA_EXPORT_SKILL_UNIVERSAL_IDENTIFIER', + ); + }); + + it('should use kebab-case for name in template', () => { + const result = getSkillBaseFile({ + name: 'my-awesome-skill', + }); + + expect(result).toContain("name: 'my-awesome-skill'"); + expect(result).toContain("label: 'my-awesome-skill'"); + }); + + it('should handle names with numbers', () => { + const result = getSkillBaseFile({ + name: 'skill-v2', + }); + + expect(result).toContain( + 'export const SKILL_V_2_SKILL_UNIVERSAL_IDENTIFIER', + ); + expect(result).toContain("label: 'skill-v2'"); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-view-base-file.spec.ts b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-view-base-file.spec.ts new file mode 100644 index 0000000000..4fe07571d1 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/entity/__tests__/get-view-base-file.spec.ts @@ -0,0 +1,115 @@ +import { getViewBaseFile } from '@/cli/utilities/entity/entity-view-template'; + +describe('getViewBaseFile', () => { + it('should render proper file using defineView', () => { + const result = getViewBaseFile({ + name: 'my-view', + universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4', + objectUniversalIdentifier: 'obj-abc-123', + }); + + expect(result).toContain("import { defineView } from 'twenty-sdk'"); + expect(result).toContain('export default defineView({'); + expect(result).toContain( + "universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4'", + ); + expect(result).toContain("name: 'my-view'"); + expect(result).toContain("objectUniversalIdentifier: 'obj-abc-123'"); + expect(result).toContain("icon: 'IconList'"); + expect(result).toContain('position: 0'); + }); + + it('should use default objectUniversalIdentifier when not provided', () => { + const result = getViewBaseFile({ + name: 'default-view', + }); + + expect(result).toContain("objectUniversalIdentifier: 'fill-later'"); + }); + + it('should include commented fields and filters when no fields provided', () => { + const result = getViewBaseFile({ + name: 'empty-view', + }); + + expect(result).toContain('// fields: ['); + expect(result).toContain('// filters: ['); + }); + + it('should render fields block when fields are provided', () => { + const result = getViewBaseFile({ + name: 'view-with-fields', + fields: [ + { + fieldMetadataUniversalIdentifier: 'field-uuid-1', + position: 0, + isVisible: true, + size: 150, + }, + { + fieldMetadataUniversalIdentifier: 'field-uuid-2', + position: 1, + }, + ], + }); + + expect(result).toContain('fields: ['); + expect(result).toContain('field-uuid-1'); + expect(result).toContain('field-uuid-2'); + expect(result).not.toContain('// fields: ['); + }); + + it('should apply default values to fields', () => { + const result = getViewBaseFile({ + name: 'view-defaults', + fields: [ + { + fieldMetadataUniversalIdentifier: 'field-uuid-1', + position: 0, + }, + ], + }); + + // Default isVisible is true, default size is 200 + expect(result).toContain('"isVisible": true'); + expect(result).toContain('"size": 200'); + }); + + it('should generate unique UUID when not provided', () => { + const result = getViewBaseFile({ + name: 'auto-uuid-view', + }); + + expect(result).toMatch( + /universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/, + ); + }); + + it('should use kebab-case for name', () => { + const result = getViewBaseFile({ + name: 'all active contacts', + }); + + expect(result).toContain("name: 'all-active-contacts'"); + }); + + it('should generate UUIDs for fields when not provided', () => { + const result = getViewBaseFile({ + name: 'view-auto-field-uuids', + fields: [ + { + fieldMetadataUniversalIdentifier: 'field-uuid-1', + position: 0, + }, + ], + }); + + // The field should get a generated universalIdentifier + const uuidRegex = + /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g; + const matches = result.match(uuidRegex); + + // At least 2 UUIDs: one for the view and one for the field + expect(matches!.length).toBeGreaterThanOrEqual(2); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/entity/entity-field-template.ts b/packages/twenty-sdk/src/cli/utilities/entity/entity-field-template.ts index 2cd4c78156..c7b1867ec5 100644 --- a/packages/twenty-sdk/src/cli/utilities/entity/entity-field-template.ts +++ b/packages/twenty-sdk/src/cli/utilities/entity/entity-field-template.ts @@ -24,7 +24,7 @@ export default defineField({ universalIdentifier: '${universalIdentifier}', name: '${data.name}', label: '${data.label}', - type: FieldMetadataType.${data.type}, + type: FieldType.${data.type}, objectUniversalIdentifier: '${data.objectUniversalIdentifier}',${descriptionLine} }); `;