@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'");
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
+60
@@ -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'");
|
||||
});
|
||||
});
|
||||
+60
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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'");
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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}
|
||||
});
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user