Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aac099f2e8 | |||
| a3c0c62506 | |||
| 390ae34ed8 | |||
| fe59c333ac | |||
| 6c0da189cb | |||
| 5aea16f070 | |||
| 2f542d6763 | |||
| 4ac38449cb | |||
| dab4cbcb7d | |||
| a73f586619 | |||
| d1126f507d | |||
| 3a1f10e572 | |||
| d16299a624 | |||
| 61d058bb1f |
@@ -1,3 +1,13 @@
|
||||
generated
|
||||
|
||||
# Ignore .twenty build output for all apps
|
||||
.twenty
|
||||
|
||||
# Allow committed fixture metadata (used by dev seeder)
|
||||
!fixtures/**/.twenty
|
||||
fixtures/**/.twenty/*
|
||||
!fixtures/**/.twenty/output
|
||||
fixtures/**/.twenty/output/**
|
||||
!fixtures/**/.twenty/output/manifest.json
|
||||
!fixtures/**/.twenty/output/package.json
|
||||
!fixtures/**/.twenty/output/yarn.lock
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import path from 'path';
|
||||
|
||||
import { buildAndValidateManifest } from '@/cli/utilities/build/manifest/build-and-validate-manifest';
|
||||
import { buildApplication } from '@/cli/utilities/build/common/build-application';
|
||||
import { manifestUpdateChecksums } from '@/cli/utilities/build/manifest/manifest-update-checksums';
|
||||
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
|
||||
|
||||
const FIXTURE_APPS = ['hello-world-app', 'postcard-app', 'minimal-app'];
|
||||
|
||||
const buildFixture = async (fixtureName: string): Promise<void> => {
|
||||
const appPath = path.resolve(__dirname, fixtureName);
|
||||
|
||||
console.log(`\n--- Building fixture: ${fixtureName} ---`);
|
||||
console.log(` App path: ${appPath}`);
|
||||
|
||||
const manifestResult = await buildAndValidateManifest(appPath);
|
||||
|
||||
if (!manifestResult.success) {
|
||||
console.error(` FAILED to build manifest: ${manifestResult.errors.join(', ')}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { manifest, filePaths } = manifestResult;
|
||||
|
||||
for (const warning of manifestResult.warnings) {
|
||||
console.warn(` Warning: ${warning}`);
|
||||
}
|
||||
|
||||
const buildResult = await buildApplication({ appPath, manifest, filePaths });
|
||||
|
||||
const updatedManifest = manifestUpdateChecksums({
|
||||
manifest,
|
||||
builtFileInfos: buildResult.builtFileInfos,
|
||||
});
|
||||
|
||||
await writeManifestToOutput(appPath, updatedManifest);
|
||||
|
||||
console.log(` Built ${buildResult.builtFileInfos.size} files`);
|
||||
console.log(` Output: ${path.join(appPath, '.twenty', 'output')}`);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
for (const fixture of FIXTURE_APPS) {
|
||||
await buildFixture(fixture);
|
||||
}
|
||||
|
||||
console.log('\nDone.');
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
generated
|
||||
node_modules
|
||||
@@ -0,0 +1,404 @@
|
||||
{
|
||||
"application": {
|
||||
"universalIdentifier": "6563e091-9f5b-4026-a3ea-7e3b3d09e218",
|
||||
"displayName": "Hello world",
|
||||
"description": "",
|
||||
"defaultRoleUniversalIdentifier": "9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f",
|
||||
"yarnLockChecksum": null,
|
||||
"packageJsonChecksum": null,
|
||||
"apiClientChecksum": "6db455b2541abbd2d4e9f85469bdc532",
|
||||
"preInstallLogicFunctionUniversalIdentifier": "1272ffdb-8e2f-492c-ab37-66c2b97e9c23",
|
||||
"postInstallLogicFunctionUniversalIdentifier": "7a3f4684-51db-494d-833b-a747a3b90507"
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"universalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"nameSingular": "exampleItem",
|
||||
"namePlural": "exampleItems",
|
||||
"labelSingular": "Example item",
|
||||
"labelPlural": "Example items",
|
||||
"description": "A sample custom object",
|
||||
"icon": "IconBox",
|
||||
"labelIdentifierFieldMetadataUniversalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
|
||||
"fields": [
|
||||
{
|
||||
"universalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
|
||||
"type": "TEXT",
|
||||
"name": "name",
|
||||
"label": "Name",
|
||||
"description": "Name of the example item",
|
||||
"icon": "IconAbc"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"label": "Id",
|
||||
"description": "Id",
|
||||
"icon": "Icon123",
|
||||
"isNullable": false,
|
||||
"defaultValue": "uuid",
|
||||
"type": "UUID",
|
||||
"universalIdentifier": "5ba62991-6034-555c-b984-5e84897eaa9f"
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"label": "Creation date",
|
||||
"description": "Creation date",
|
||||
"icon": "IconCalendar",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "8b616045-237c-574d-8689-73f06acff8d8"
|
||||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"label": "Last update",
|
||||
"description": "Last time the record was changed",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "ced5587d-d9de-51bf-9ad0-74af6198fca4"
|
||||
},
|
||||
{
|
||||
"name": "deletedAt",
|
||||
"label": "Deleted at",
|
||||
"description": "Deletion date",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "674ee694-0b0f-557f-993c-2b1404db0811"
|
||||
},
|
||||
{
|
||||
"name": "createdBy",
|
||||
"label": "Created by",
|
||||
"description": "The creator of the record",
|
||||
"icon": "IconCreativeCommonsSa",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "5ed1503f-2803-59d1-9b27-e4f1701b8c2a"
|
||||
},
|
||||
{
|
||||
"name": "updatedBy",
|
||||
"label": "Updated by",
|
||||
"description": "The workspace member who last updated the record",
|
||||
"icon": "IconUserCircle",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "3d3a80b4-14a0-55a0-b534-1b23f043c1d5"
|
||||
},
|
||||
{
|
||||
"name": "position",
|
||||
"label": "Position",
|
||||
"description": "Position",
|
||||
"icon": "IconHierarchy2",
|
||||
"isNullable": false,
|
||||
"defaultValue": 0,
|
||||
"type": "POSITION",
|
||||
"universalIdentifier": "a714b336-d765-5dff-b34b-fcc6ab037e3b"
|
||||
},
|
||||
{
|
||||
"name": "searchVector",
|
||||
"label": "Search vector",
|
||||
"icon": "IconSearch",
|
||||
"description": "Search vector",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "TS_VECTOR",
|
||||
"universalIdentifier": "068064e1-c9da-5772-9293-9e57183cee67"
|
||||
},
|
||||
{
|
||||
"name": "timelineActivities",
|
||||
"label": "Timeline Activities",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "e5feca94-536c-5779-8c33-05e39705bcd7",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3d140ea1-fc57-5c8c-96fd-7d1525ed6a69",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5"
|
||||
},
|
||||
{
|
||||
"name": "favorites",
|
||||
"label": "Favorites",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "3781cc69-9d3f-58d0-927c-c1c552f37a1d",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3043fec0-091c-59ff-95ef-b9d95fbfa3e6",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860"
|
||||
},
|
||||
{
|
||||
"name": "attachments",
|
||||
"label": "Attachments",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "3ad98da8-42e1-50e3-b73b-8c06fa720e2a",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "e1232464-8f83-5459-aa94-a40cda8d949a",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a"
|
||||
},
|
||||
{
|
||||
"name": "noteTargets",
|
||||
"label": "Note Targets",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "67cae4ab-7d3d-5a66-8d06-ca24568ebe76",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400"
|
||||
},
|
||||
{
|
||||
"name": "taskTargets",
|
||||
"label": "Task Targets",
|
||||
"description": "Example items tied to the ExampleItem",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "a3b9f10c-f780-58cc-bbc9-402ed5f136f6",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "5d46d303-452d-5c9c-ab64-0a9768b87956",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"objectUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"universalIdentifier": "770d32c2-cf12-4ab2-b66d-73f92dc239b5",
|
||||
"type": "NUMBER",
|
||||
"name": "priority",
|
||||
"label": "Priority",
|
||||
"description": "Priority level for the example item (1-10)"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconTimelineEvent",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "3d140ea1-fc57-5c8c-96fd-7d1525ed6a69",
|
||||
"objectUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "e5feca94-536c-5779-8c33-05e39705bcd7",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconHeart",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "3043fec0-091c-59ff-95ef-b9d95fbfa3e6",
|
||||
"objectUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3781cc69-9d3f-58d0-927c-c1c552f37a1d",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "RELATION"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconFileImport",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "e1232464-8f83-5459-aa94-a40cda8d949a",
|
||||
"objectUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "3ad98da8-42e1-50e3-b73b-8c06fa720e2a",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f634-435d-ab8d-e1168b375c69"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318",
|
||||
"objectUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "67cae4ab-7d3d-5a66-8d06-ca24568ebe76",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f635-435d-ab8d-e1168b375c70"
|
||||
},
|
||||
{
|
||||
"name": "targetExampleItem",
|
||||
"label": "ExampleItem",
|
||||
"description": "ExampleItem Example item",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetExampleItemId"
|
||||
},
|
||||
"universalIdentifier": "5d46d303-452d-5c9c-ab64-0a9768b87956",
|
||||
"objectUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "a3b9f10c-f780-58cc-bbc9-402ed5f136f6",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f636-435d-ab8d-e1168b375c71"
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"universalIdentifier": "9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f",
|
||||
"label": "Hello world default function role",
|
||||
"description": "Hello world default function role",
|
||||
"canReadAllObjectRecords": true,
|
||||
"canUpdateAllObjectRecords": true,
|
||||
"canSoftDeleteAllObjectRecords": true,
|
||||
"canDestroyAllObjectRecords": false
|
||||
}
|
||||
],
|
||||
"skills": [
|
||||
{
|
||||
"universalIdentifier": "d0940029-9d3c-40be-903a-52d65393028f",
|
||||
"name": "example-skill",
|
||||
"label": "Example Skill",
|
||||
"description": "A sample skill for your application",
|
||||
"icon": "IconBrain",
|
||||
"content": "Add your skill instructions here. Skills provide context and capabilities to AI agents."
|
||||
}
|
||||
],
|
||||
"agents": [],
|
||||
"logicFunctions": [
|
||||
{
|
||||
"universalIdentifier": "2baa26eb-9aaf-4856-a4f4-30d6fd6480ee",
|
||||
"name": "hello-world-logic-function",
|
||||
"description": "A simple logic function",
|
||||
"timeoutSeconds": 5,
|
||||
"httpRouteTriggerSettings": {
|
||||
"path": "/hello-world-logic-function",
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false
|
||||
},
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "src/logic-functions/hello-world.ts",
|
||||
"builtHandlerPath": "src/logic-functions/hello-world.mjs",
|
||||
"builtHandlerChecksum": "f1a0565e18366e6c531438631137d23b"
|
||||
},
|
||||
{
|
||||
"universalIdentifier": "7a3f4684-51db-494d-833b-a747a3b90507",
|
||||
"name": "post-install",
|
||||
"description": "Runs after installation to set up the application.",
|
||||
"timeoutSeconds": 300,
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "src/logic-functions/post-install.ts",
|
||||
"builtHandlerPath": "src/logic-functions/post-install.mjs",
|
||||
"builtHandlerChecksum": "7b016a1819c4e2355a80a895464032a5"
|
||||
},
|
||||
{
|
||||
"universalIdentifier": "1272ffdb-8e2f-492c-ab37-66c2b97e9c23",
|
||||
"name": "pre-install",
|
||||
"description": "Runs before installation to prepare the application.",
|
||||
"timeoutSeconds": 300,
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "src/logic-functions/pre-install.ts",
|
||||
"builtHandlerPath": "src/logic-functions/pre-install.mjs",
|
||||
"builtHandlerChecksum": "e2151e31924929390a5933ebf7419465"
|
||||
}
|
||||
],
|
||||
"frontComponents": [
|
||||
{
|
||||
"universalIdentifier": "d371f098-5b2c-42f0-898d-94459f1ee337",
|
||||
"name": "hello-world-front-component",
|
||||
"description": "A sample front component",
|
||||
"componentName": "HelloWorld",
|
||||
"sourceComponentPath": "src/front-components/hello-world.tsx",
|
||||
"builtComponentPath": "src/front-components/hello-world.mjs",
|
||||
"builtComponentChecksum": "826d5d3f633496d7c2a4f902589b83ed",
|
||||
"isHeadless": false
|
||||
}
|
||||
],
|
||||
"publicAssets": [],
|
||||
"views": [
|
||||
{
|
||||
"universalIdentifier": "e004df40-29f3-47ba-b39d-d3a5c444367a",
|
||||
"name": "All example items",
|
||||
"objectUniversalIdentifier": "dfd43356-39b3-4b55-b4a7-279bec689928",
|
||||
"icon": "IconList",
|
||||
"key": "INDEX",
|
||||
"position": 0,
|
||||
"fields": [
|
||||
{
|
||||
"universalIdentifier": "496c40c2-5766-419c-93bf-20fdad3f34bb",
|
||||
"fieldMetadataUniversalIdentifier": "d2d7f6cd-33f6-456f-bf00-17adeca926ba",
|
||||
"position": 0,
|
||||
"isVisible": true,
|
||||
"size": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"navigationMenuItems": [
|
||||
{
|
||||
"universalIdentifier": "10f90627-e9c2-44b7-9742-bed77e3d1b17",
|
||||
"name": "example-navigation-menu-item",
|
||||
"icon": "IconList",
|
||||
"color": "blue",
|
||||
"position": 0,
|
||||
"viewUniversalIdentifier": "e004df40-29f3-47ba-b39d-d3a5c444367a"
|
||||
}
|
||||
],
|
||||
"pageLayouts": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "hello-world-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"app:dev": "twenty app:dev",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from './src/roles/default-role';
|
||||
|
||||
export const APPLICATION_UNIVERSAL_IDENTIFIER =
|
||||
'6563e091-9f5b-4026-a3ea-7e3b3d09e218';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
displayName: 'Hello world',
|
||||
description: '',
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "hello-world-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"app:dev": "twenty app:dev",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineField, FieldType } from 'twenty-sdk';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
|
||||
export default defineField({
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
universalIdentifier: '770d32c2-cf12-4ab2-b66d-73f92dc239b5',
|
||||
type: FieldType.NUMBER,
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
description: 'Priority level for the example item (1-10)',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineFrontComponent } from 'twenty-sdk';
|
||||
|
||||
export const HelloWorld = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>Hello, World!</h1>
|
||||
<p>This is your first front component.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'd371f098-5b2c-42f0-898d-94459f1ee337',
|
||||
name: 'hello-world-front-component',
|
||||
description: 'A sample front component',
|
||||
component: HelloWorld,
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (): Promise<{ message: string }> => {
|
||||
return { message: 'Hello, World!' };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '2baa26eb-9aaf-4856-a4f4-30d6fd6480ee',
|
||||
name: 'hello-world-logic-function',
|
||||
description: 'A simple logic function',
|
||||
timeoutSeconds: 5,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/hello-world-logic-function',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '7a3f4684-51db-494d-833b-a747a3b90507',
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '1272ffdb-8e2f-492c-ab37-66c2b97e9c23',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { defineNavigationMenuItem } from 'twenty-sdk';
|
||||
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '10f90627-e9c2-44b7-9742-bed77e3d1b17',
|
||||
name: 'example-navigation-menu-item',
|
||||
icon: 'IconList',
|
||||
color: 'blue',
|
||||
position: 0,
|
||||
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { defineObject, FieldType } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER =
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928';
|
||||
|
||||
export const NAME_FIELD_UNIVERSAL_IDENTIFIER =
|
||||
'd2d7f6cd-33f6-456f-bf00-17adeca926ba';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
nameSingular: 'exampleItem',
|
||||
namePlural: 'exampleItems',
|
||||
labelSingular: 'Example item',
|
||||
labelPlural: 'Example items',
|
||||
description: 'A sample custom object',
|
||||
icon: 'IconBox',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.TEXT,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
description: 'Name of the example item',
|
||||
icon: 'IconAbc',
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { defineRole } from 'twenty-sdk';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Hello world default function role',
|
||||
description: 'Hello world default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: false,
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineSkill } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER =
|
||||
'd0940029-9d3c-40be-903a-52d65393028f';
|
||||
|
||||
export default defineSkill({
|
||||
universalIdentifier: EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'example-skill',
|
||||
label: 'Example Skill',
|
||||
description: 'A sample skill for your application',
|
||||
icon: 'IconBrain',
|
||||
content: 'Add your skill instructions here. Skills provide context and capabilities to AI agents.',
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { defineView } from 'twenty-sdk';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
|
||||
|
||||
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = 'e004df40-29f3-47ba-b39d-d3a5c444367a';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
name: 'All example items',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
icon: 'IconList',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: '496c40c2-5766-419c-93bf-20fdad3f34bb',
|
||||
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*"],
|
||||
"exclude": ["node_modules", "dist", ".twenty"]
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
{
|
||||
"application": {
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000001",
|
||||
"displayName": "Root App",
|
||||
"description": "An app with all entities at root level",
|
||||
"icon": "IconFolder",
|
||||
"defaultRoleUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000002",
|
||||
"yarnLockChecksum": "d41d8cd98f00b204e9800998ecf8427e",
|
||||
"packageJsonChecksum": "ac87abb5113d9d09be71481efc123800",
|
||||
"apiClientChecksum": null
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"nameSingular": "myNote",
|
||||
"namePlural": "myNotes",
|
||||
"labelSingular": "My note",
|
||||
"labelPlural": "My notes",
|
||||
"description": "A simple root-level object",
|
||||
"icon": "IconNote",
|
||||
"labelIdentifierFieldMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000031",
|
||||
"fields": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000031",
|
||||
"type": "TEXT",
|
||||
"label": "Title",
|
||||
"name": "title"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"label": "Id",
|
||||
"description": "Id",
|
||||
"icon": "Icon123",
|
||||
"isNullable": false,
|
||||
"defaultValue": "uuid",
|
||||
"type": "UUID",
|
||||
"universalIdentifier": "5bdb4f57-a349-56d0-b421-845fd55b3026"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Name",
|
||||
"description": "Name",
|
||||
"icon": "IconAbc",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "TEXT",
|
||||
"universalIdentifier": "86cc7b06-26de-5b7b-bf4a-31fa246dfd90"
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"label": "Creation date",
|
||||
"description": "Creation date",
|
||||
"icon": "IconCalendar",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "abe3bc69-1a51-5814-a722-74f5a025e92a"
|
||||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"label": "Last update",
|
||||
"description": "Last time the record was changed",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": false,
|
||||
"defaultValue": "now",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "c296d408-9720-5a86-8586-3490fbf04b86"
|
||||
},
|
||||
{
|
||||
"name": "deletedAt",
|
||||
"label": "Deleted at",
|
||||
"description": "Deletion date",
|
||||
"icon": "IconCalendarClock",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "488333a2-f4ff-5790-88ef-21f07f8f1985"
|
||||
},
|
||||
{
|
||||
"name": "createdBy",
|
||||
"label": "Created by",
|
||||
"description": "The creator of the record",
|
||||
"icon": "IconCreativeCommonsSa",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "fba6e01b-6579-554e-ad64-a676003c8e68"
|
||||
},
|
||||
{
|
||||
"name": "updatedBy",
|
||||
"label": "Updated by",
|
||||
"description": "The workspace member who last updated the record",
|
||||
"icon": "IconUserCircle",
|
||||
"isNullable": false,
|
||||
"defaultValue": {
|
||||
"name": "''",
|
||||
"source": "'MANUAL'"
|
||||
},
|
||||
"type": "ACTOR",
|
||||
"universalIdentifier": "fa60f87d-15c8-5ff7-9da0-191fa14a922f"
|
||||
},
|
||||
{
|
||||
"name": "position",
|
||||
"label": "Position",
|
||||
"description": "Position",
|
||||
"icon": "IconHierarchy2",
|
||||
"isNullable": false,
|
||||
"defaultValue": 0,
|
||||
"type": "POSITION",
|
||||
"universalIdentifier": "4a5d7791-ddc9-53ee-bc46-699da7373290"
|
||||
},
|
||||
{
|
||||
"name": "searchVector",
|
||||
"label": "Search vector",
|
||||
"icon": "IconSearch",
|
||||
"description": "Search vector",
|
||||
"isNullable": true,
|
||||
"defaultValue": null,
|
||||
"type": "TS_VECTOR",
|
||||
"universalIdentifier": "0f00bc3b-f313-5206-8496-842af20a538a"
|
||||
},
|
||||
{
|
||||
"name": "timelineActivities",
|
||||
"label": "Timeline Activities",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "7e28b1ea-9a94-5d36-bc03-3f12f476867a",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5"
|
||||
},
|
||||
{
|
||||
"name": "favorites",
|
||||
"label": "Favorites",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "ed80c73b-25ff-5a3c-99c7-e27fa2f2611e",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "698f12fe-3feb-55e3-b6cc-73375f1a8ae6",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860"
|
||||
},
|
||||
{
|
||||
"name": "attachments",
|
||||
"label": "Attachments",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "56a872c8-7a1e-563d-8772-3978c2c5c220",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a"
|
||||
},
|
||||
{
|
||||
"name": "noteTargets",
|
||||
"label": "Note Targets",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "011595e8-7524-5ec7-9fbc-4bd3c42d7d24",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "94b6f269-fc9a-5bcd-af5b-2bdac69f276b",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400"
|
||||
},
|
||||
{
|
||||
"name": "taskTargets",
|
||||
"label": "Task Targets",
|
||||
"description": "My notes tied to the MyNote",
|
||||
"icon": "IconBuildingSkyscraper",
|
||||
"isNullable": true,
|
||||
"type": "RELATION",
|
||||
"universalSettings": {
|
||||
"relationType": "ONE_TO_MANY"
|
||||
},
|
||||
"universalIdentifier": "dd734fa3-26c3-53dd-91aa-577c313e1fa4",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconTimelineEvent",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "7e28b1ea-9a94-5d36-bc03-3f12f476867a",
|
||||
"objectUniversalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconHeart",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "698f12fe-3feb-55e3-b6cc-73375f1a8ae6",
|
||||
"objectUniversalIdentifier": "20202020-ab56-4e05-92a3-e2414a499860",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "ed80c73b-25ff-5a3c-99c7-e27fa2f2611e",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "RELATION"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconFileImport",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5",
|
||||
"objectUniversalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "56a872c8-7a1e-563d-8772-3978c2c5c220",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f634-435d-ab8d-e1168b375c69"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "94b6f269-fc9a-5bcd-af5b-2bdac69f276b",
|
||||
"objectUniversalIdentifier": "20202020-fff0-4b44-be82-bda313884400",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "011595e8-7524-5ec7-9fbc-4bd3c42d7d24",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f635-435d-ab8d-e1168b375c70"
|
||||
},
|
||||
{
|
||||
"name": "targetMyNote",
|
||||
"label": "MyNote",
|
||||
"description": "MyNote My note",
|
||||
"icon": "IconCheckbox",
|
||||
"isNullable": true,
|
||||
"universalSettings": {
|
||||
"relationType": "MANY_TO_ONE",
|
||||
"onDelete": "SET_NULL",
|
||||
"joinColumnName": "targetMyNoteId"
|
||||
},
|
||||
"universalIdentifier": "50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3",
|
||||
"objectUniversalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1",
|
||||
"relationTargetFieldMetadataUniversalIdentifier": "dd734fa3-26c3-53dd-91aa-577c313e1fa4",
|
||||
"relationTargetObjectMetadataUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"type": "MORPH_RELATION",
|
||||
"morphId": "20202020-f636-435d-ab8d-e1168b375c71"
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000040",
|
||||
"label": "My role",
|
||||
"description": "A simple root-level role",
|
||||
"canReadAllObjectRecords": true,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"canBeAssignedToAgents": false,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canBeAssignedToApiKeys": false
|
||||
}
|
||||
],
|
||||
"skills": [],
|
||||
"agents": [],
|
||||
"logicFunctions": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000010",
|
||||
"name": "my-function",
|
||||
"timeoutSeconds": 5,
|
||||
"httpRouteTriggerSettings": {
|
||||
"path": "/my-function",
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false
|
||||
},
|
||||
"toolInputSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"handlerName": "default.config.handler",
|
||||
"sourceHandlerPath": "my.function.ts",
|
||||
"builtHandlerPath": "my.function.mjs",
|
||||
"builtHandlerChecksum": "71f0af6b9dfeef884cc81ae75ca6ecb0"
|
||||
}
|
||||
],
|
||||
"frontComponents": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000020",
|
||||
"name": "my-component",
|
||||
"description": "A root-level front component",
|
||||
"componentName": "MyComponent",
|
||||
"sourceComponentPath": "my.front-component.tsx",
|
||||
"builtComponentPath": "my.front-component.mjs",
|
||||
"builtComponentChecksum": "37b905c5467584f03fa5ab2c8a05bc88",
|
||||
"isHeadless": false
|
||||
}
|
||||
],
|
||||
"publicAssets": [],
|
||||
"views": [],
|
||||
"navigationMenuItems": [],
|
||||
"pageLayouts": []
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "minimal-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "postcard-app",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"scripts": {
|
||||
"auth:login": "twenty auth:login",
|
||||
"auth:logout": "twenty auth:logout",
|
||||
"auth:status": "twenty auth:status",
|
||||
"auth:switch": "twenty auth:switch",
|
||||
"auth:list": "twenty auth:list",
|
||||
"app:dev": "twenty app:dev",
|
||||
"entity:add": "twenty entity:add",
|
||||
"function:logs": "twenty function:logs",
|
||||
"function:execute": "twenty function:execute",
|
||||
"app:uninstall": "twenty app:uninstall",
|
||||
"help": "twenty help",
|
||||
"lint": "oxlint -c .oxlintrc.json .",
|
||||
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-sdk": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"oxlint": "^0.16.0"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { resolve } from 'path';
|
||||
|
||||
const FIXTURES_ROOT = resolve(__dirname, '../../../../../twenty-apps/fixtures');
|
||||
const TWENTY_APPS_ROOT = resolve(__dirname, '../../../../../twenty-apps');
|
||||
|
||||
export const POSTCARD_APP_PATH = resolve(FIXTURES_ROOT, 'postcard-app');
|
||||
export const MINIMAL_APP_PATH = resolve(FIXTURES_ROOT, 'minimal-app');
|
||||
@@ -9,3 +10,4 @@ export const FUNCTION_EXECUTE_APP_PATH = resolve(
|
||||
FIXTURES_ROOT,
|
||||
'function-execute-app',
|
||||
);
|
||||
export const HELLO_WORLD_APP_PATH = resolve(TWENTY_APPS_ROOT, 'hello-world');
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { runAppDevInProcess } from '@/cli/__tests__/integration/utils/run-app-dev-in-process.util';
|
||||
import { HELLO_WORLD_APP_PATH } from '@/cli/__tests__/apps/fixture-paths';
|
||||
|
||||
import { defineManifestTests } from './tests/manifest.tests';
|
||||
|
||||
describe('hello-world app:dev', () => {
|
||||
beforeAll(async () => {
|
||||
const result = await runAppDevInProcess({ appPath: HELLO_WORLD_APP_PATH });
|
||||
|
||||
if (!result.success) {
|
||||
const diagnostics = JSON.stringify(
|
||||
{ events: result.events, stepStatuses: result.stepStatuses },
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`app:dev did not produce manifest.json within timeout.\n${diagnostics}`,
|
||||
);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
defineManifestTests(HELLO_WORLD_APP_PATH);
|
||||
});
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
import { FieldType } from '@/sdk';
|
||||
import type { Manifest } from 'twenty-shared/application';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
RelationType,
|
||||
ViewKey,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
export const EXPECTED_MANIFEST: Manifest = {
|
||||
application: {
|
||||
universalIdentifier: '6563e091-9f5b-4026-a3ea-7e3b3d09e218',
|
||||
displayName: 'Hello world',
|
||||
description: '',
|
||||
defaultRoleUniversalIdentifier: '9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f',
|
||||
yarnLockChecksum: '[checksum]',
|
||||
packageJsonChecksum: '[checksum]',
|
||||
apiClientChecksum: null,
|
||||
preInstallLogicFunctionUniversalIdentifier:
|
||||
'1272ffdb-8e2f-492c-ab37-66c2b97e9c23',
|
||||
postInstallLogicFunctionUniversalIdentifier:
|
||||
'7a3f4684-51db-494d-833b-a747a3b90507',
|
||||
},
|
||||
objects: [
|
||||
{
|
||||
universalIdentifier: 'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
nameSingular: 'exampleItem',
|
||||
namePlural: 'exampleItems',
|
||||
labelSingular: 'Example item',
|
||||
labelPlural: 'Example items',
|
||||
description: 'A sample custom object',
|
||||
icon: 'IconBox',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
'd2d7f6cd-33f6-456f-bf00-17adeca926ba',
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: 'd2d7f6cd-33f6-456f-bf00-17adeca926ba',
|
||||
type: FieldType.TEXT,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
description: 'Name of the example item',
|
||||
icon: 'IconAbc',
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
label: 'Id',
|
||||
description: 'Id',
|
||||
icon: 'Icon123',
|
||||
isNullable: false,
|
||||
defaultValue: 'uuid',
|
||||
type: FieldMetadataType.UUID,
|
||||
universalIdentifier: '5ba62991-6034-555c-b984-5e84897eaa9f',
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
label: 'Creation date',
|
||||
description: 'Creation date',
|
||||
icon: 'IconCalendar',
|
||||
isNullable: false,
|
||||
defaultValue: 'now',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '8b616045-237c-574d-8689-73f06acff8d8',
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
label: 'Last update',
|
||||
description: 'Last time the record was changed',
|
||||
icon: 'IconCalendarClock',
|
||||
isNullable: false,
|
||||
defaultValue: 'now',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: 'ced5587d-d9de-51bf-9ad0-74af6198fca4',
|
||||
},
|
||||
{
|
||||
name: 'deletedAt',
|
||||
label: 'Deleted at',
|
||||
description: 'Deletion date',
|
||||
icon: 'IconCalendarClock',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '674ee694-0b0f-557f-993c-2b1404db0811',
|
||||
},
|
||||
{
|
||||
name: 'createdBy',
|
||||
label: 'Created by',
|
||||
description: 'The creator of the record',
|
||||
icon: 'IconCreativeCommonsSa',
|
||||
isNullable: false,
|
||||
defaultValue: {
|
||||
name: "''",
|
||||
source: "'MANUAL'",
|
||||
},
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '5ed1503f-2803-59d1-9b27-e4f1701b8c2a',
|
||||
},
|
||||
{
|
||||
name: 'updatedBy',
|
||||
label: 'Updated by',
|
||||
description: 'The workspace member who last updated the record',
|
||||
icon: 'IconUserCircle',
|
||||
isNullable: false,
|
||||
defaultValue: {
|
||||
name: "''",
|
||||
source: "'MANUAL'",
|
||||
},
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '3d3a80b4-14a0-55a0-b534-1b23f043c1d5',
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
label: 'Position',
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
defaultValue: 0,
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: 'a714b336-d765-5dff-b34b-fcc6ab037e3b',
|
||||
},
|
||||
{
|
||||
name: 'searchVector',
|
||||
label: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
description: 'Search vector',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '068064e1-c9da-5772-9293-9e57183cee67',
|
||||
},
|
||||
{
|
||||
name: 'timelineActivities',
|
||||
label: 'Timeline Activities',
|
||||
description: 'Example items tied to the ExampleItem',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: true,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: 'e5feca94-536c-5779-8c33-05e39705bcd7',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'3d140ea1-fc57-5c8c-96fd-7d1525ed6a69',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
},
|
||||
{
|
||||
name: 'favorites',
|
||||
label: 'Favorites',
|
||||
description: 'Example items tied to the ExampleItem',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: true,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '3781cc69-9d3f-58d0-927c-c1c552f37a1d',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'3043fec0-091c-59ff-95ef-b9d95fbfa3e6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-ab56-4e05-92a3-e2414a499860',
|
||||
},
|
||||
{
|
||||
name: 'attachments',
|
||||
label: 'Attachments',
|
||||
description: 'Example items tied to the ExampleItem',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: true,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '3ad98da8-42e1-50e3-b73b-8c06fa720e2a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'e1232464-8f83-5459-aa94-a40cda8d949a',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
},
|
||||
{
|
||||
name: 'noteTargets',
|
||||
label: 'Note Targets',
|
||||
description: 'Example items tied to the ExampleItem',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: true,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '67cae4ab-7d3d-5a66-8d06-ca24568ebe76',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
},
|
||||
{
|
||||
name: 'taskTargets',
|
||||
label: 'Task Targets',
|
||||
description: 'Example items tied to the ExampleItem',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: true,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: 'a3b9f10c-f780-58cc-bbc9-402ed5f136f6',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'5d46d303-452d-5c9c-ab64-0a9768b87956',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
objectUniversalIdentifier: 'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
universalIdentifier: '770d32c2-cf12-4ab2-b66d-73f92dc239b5',
|
||||
type: FieldType.NUMBER,
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
description: 'Priority level for the example item (1-10)',
|
||||
},
|
||||
{
|
||||
name: 'targetExampleItem',
|
||||
label: 'ExampleItem',
|
||||
description: 'ExampleItem Example item',
|
||||
icon: 'IconTimelineEvent',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetExampleItemId',
|
||||
},
|
||||
universalIdentifier: '3d140ea1-fc57-5c8c-96fd-7d1525ed6a69',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'e5feca94-536c-5779-8c33-05e39705bcd7',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
|
||||
},
|
||||
{
|
||||
name: 'targetExampleItem',
|
||||
label: 'ExampleItem',
|
||||
description: 'ExampleItem Example item',
|
||||
icon: 'IconHeart',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetExampleItemId',
|
||||
},
|
||||
universalIdentifier: '3043fec0-091c-59ff-95ef-b9d95fbfa3e6',
|
||||
objectUniversalIdentifier: '20202020-ab56-4e05-92a3-e2414a499860',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'3781cc69-9d3f-58d0-927c-c1c552f37a1d',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
type: FieldType.RELATION,
|
||||
},
|
||||
{
|
||||
name: 'targetExampleItem',
|
||||
label: 'ExampleItem',
|
||||
description: 'ExampleItem Example item',
|
||||
icon: 'IconFileImport',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetExampleItemId',
|
||||
},
|
||||
universalIdentifier: 'e1232464-8f83-5459-aa94-a40cda8d949a',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'3ad98da8-42e1-50e3-b73b-8c06fa720e2a',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
morphId: '20202020-f634-435d-ab8d-e1168b375c69',
|
||||
},
|
||||
{
|
||||
name: 'targetExampleItem',
|
||||
label: 'ExampleItem',
|
||||
description: 'ExampleItem Example item',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetExampleItemId',
|
||||
},
|
||||
universalIdentifier: '9eeb7b0d-37e4-5a93-97c9-3dbb77fdb318',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'67cae4ab-7d3d-5a66-8d06-ca24568ebe76',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
morphId: '20202020-f635-435d-ab8d-e1168b375c70',
|
||||
},
|
||||
{
|
||||
name: 'targetExampleItem',
|
||||
label: 'ExampleItem',
|
||||
description: 'ExampleItem Example item',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetExampleItemId',
|
||||
},
|
||||
universalIdentifier: '5d46d303-452d-5c9c-ab64-0a9768b87956',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'a3b9f10c-f780-58cc-bbc9-402ed5f136f6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
morphId: '20202020-f636-435d-ab8d-e1168b375c71',
|
||||
},
|
||||
],
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: '9238bc7b-d38f-4a1c-9d19-31ab7bc67a2f',
|
||||
label: 'Hello world default function role',
|
||||
description: 'Hello world default function role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: false,
|
||||
},
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: 'd0940029-9d3c-40be-903a-52d65393028f',
|
||||
name: 'example-skill',
|
||||
label: 'Example Skill',
|
||||
description: 'A sample skill for your application',
|
||||
icon: 'IconBrain',
|
||||
content:
|
||||
'Add your skill instructions here. Skills provide context and capabilities to AI agents.',
|
||||
},
|
||||
],
|
||||
agents: [],
|
||||
logicFunctions: [
|
||||
{
|
||||
universalIdentifier: '2baa26eb-9aaf-4856-a4f4-30d6fd6480ee',
|
||||
name: 'hello-world-logic-function',
|
||||
description: 'A simple logic function',
|
||||
timeoutSeconds: 5,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/hello-world-logic-function',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
toolInputSchema: { type: 'object', properties: {} },
|
||||
handlerName: 'default.config.handler',
|
||||
sourceHandlerPath: 'src/logic-functions/hello-world.ts',
|
||||
builtHandlerPath: 'src/logic-functions/hello-world.mjs',
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
},
|
||||
{
|
||||
universalIdentifier: '7a3f4684-51db-494d-833b-a747a3b90507',
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
toolInputSchema: { type: 'object', properties: {} },
|
||||
handlerName: 'default.config.handler',
|
||||
sourceHandlerPath: 'src/logic-functions/post-install.ts',
|
||||
builtHandlerPath: 'src/logic-functions/post-install.mjs',
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
},
|
||||
{
|
||||
universalIdentifier: '1272ffdb-8e2f-492c-ab37-66c2b97e9c23',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
toolInputSchema: { type: 'object', properties: {} },
|
||||
handlerName: 'default.config.handler',
|
||||
sourceHandlerPath: 'src/logic-functions/pre-install.ts',
|
||||
builtHandlerPath: 'src/logic-functions/pre-install.mjs',
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
},
|
||||
],
|
||||
frontComponents: [
|
||||
{
|
||||
universalIdentifier: 'd371f098-5b2c-42f0-898d-94459f1ee337',
|
||||
name: 'hello-world-front-component',
|
||||
description: 'A sample front component',
|
||||
componentName: 'HelloWorld',
|
||||
sourceComponentPath: 'src/front-components/hello-world.tsx',
|
||||
builtComponentPath: 'src/front-components/hello-world.mjs',
|
||||
builtComponentChecksum: '[checksum]',
|
||||
isHeadless: false,
|
||||
},
|
||||
],
|
||||
publicAssets: [],
|
||||
views: [
|
||||
{
|
||||
universalIdentifier: 'e004df40-29f3-47ba-b39d-d3a5c444367a',
|
||||
name: 'All example items',
|
||||
objectUniversalIdentifier: 'dfd43356-39b3-4b55-b4a7-279bec689928',
|
||||
icon: 'IconList',
|
||||
key: ViewKey.INDEX,
|
||||
position: 0,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: '496c40c2-5766-419c-93bf-20fdad3f34bb',
|
||||
fieldMetadataUniversalIdentifier:
|
||||
'd2d7f6cd-33f6-456f-bf00-17adeca926ba',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navigationMenuItems: [
|
||||
{
|
||||
universalIdentifier: '10f90627-e9c2-44b7-9742-bed77e3d1b17',
|
||||
name: 'example-navigation-menu-item',
|
||||
icon: 'IconList',
|
||||
color: 'blue',
|
||||
position: 0,
|
||||
viewUniversalIdentifier: 'e004df40-29f3-47ba-b39d-d3a5c444367a',
|
||||
},
|
||||
],
|
||||
pageLayouts: [],
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { normalizeManifestForComparison } from '@/cli/__tests__/integration/utils/normalize-manifest.util';
|
||||
import { readJson } from '@/cli/utilities/file/fs-utils';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { EXPECTED_MANIFEST } from '../expected-manifest';
|
||||
|
||||
export const defineManifestTests = (appPath: string): void => {
|
||||
const manifestOutputPath = join(appPath, '.twenty/output/manifest.json');
|
||||
|
||||
describe('manifest', () => {
|
||||
it('should build manifest matching expected JSON', async () => {
|
||||
const manifest = await readJson<Manifest>(manifestOutputPath);
|
||||
|
||||
expect(manifest).not.toBeNull();
|
||||
|
||||
expect(manifest.objects).toHaveLength(1);
|
||||
expect(manifest.logicFunctions).toHaveLength(3);
|
||||
expect(manifest.frontComponents).toHaveLength(1);
|
||||
expect(manifest.roles).toHaveLength(1);
|
||||
expect(manifest.skills).toHaveLength(1);
|
||||
expect(manifest.fields).toHaveLength(6);
|
||||
expect(manifest.views).toHaveLength(1);
|
||||
expect(manifest.navigationMenuItems).toHaveLength(1);
|
||||
|
||||
expect(normalizeManifestForComparison(manifest)).toEqual(
|
||||
normalizeManifestForComparison(EXPECTED_MANIFEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -2857,6 +2857,8 @@ type Query {
|
||||
currentUser: User!
|
||||
currentWorkspace: Workspace!
|
||||
getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData!
|
||||
findManyApplications: [Application!]!
|
||||
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
|
||||
checkUserExists(email: String!, captchaToken: String): CheckUserExist!
|
||||
checkWorkspaceInviteHashIsValid(inviteHash: String!): WorkspaceInviteHashValid!
|
||||
findWorkspaceFromInviteHash(inviteHash: String!): Workspace!
|
||||
@@ -2908,8 +2910,6 @@ type Query {
|
||||
getEmailingDomains: [EmailingDomain!]!
|
||||
findManyMarketplaceApps: [MarketplaceApp!]!
|
||||
findOneMarketplaceApp(universalIdentifier: String!): MarketplaceApp!
|
||||
findManyApplications: [Application!]!
|
||||
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
|
||||
}
|
||||
|
||||
input LogicFunctionIdInput {
|
||||
@@ -3130,6 +3130,10 @@ type Mutation {
|
||||
updateWorkspace(data: UpdateWorkspaceInput!): Workspace!
|
||||
deleteCurrentWorkspace: Workspace!
|
||||
checkCustomDomainValidRecords: DomainValidRecords
|
||||
installApplication(appRegistrationId: String!, version: String): Boolean!
|
||||
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
|
||||
uninstallApplication(universalIdentifier: String!): Boolean!
|
||||
updateOneApplicationVariable(key: String!, value: String!, applicationId: UUID!): Boolean!
|
||||
getAuthorizationUrlForSSO(input: GetAuthorizationUrlForSSOInput!): GetAuthorizationUrlForSSO!
|
||||
getLoginTokenFromCredentials(email: String!, password: String!, captchaToken: String, locale: String, verifyEmailRedirectPath: String, origin: String!): LoginToken!
|
||||
signIn(email: String!, password: String!, captchaToken: String, locale: String, verifyEmailRedirectPath: String): AvailableWorkspacesAndAccessTokens!
|
||||
@@ -3197,10 +3201,6 @@ type Mutation {
|
||||
verifyEmailingDomain(id: String!): EmailingDomain!
|
||||
createOneAppToken(input: CreateOneAppTokenInput!): AppToken!
|
||||
installMarketplaceApp(universalIdentifier: String!, version: String): Boolean!
|
||||
installApplication(appRegistrationId: String!, version: String): Boolean!
|
||||
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
|
||||
uninstallApplication(universalIdentifier: String!): Boolean!
|
||||
updateOneApplicationVariable(key: String!, value: String!, applicationId: UUID!): Boolean!
|
||||
createDevelopmentApplication(universalIdentifier: String!, name: String!): DevelopmentApplication!
|
||||
generateApplicationToken(applicationId: UUID!): ApplicationTokenPair!
|
||||
syncApplication(manifest: JSON!): WorkspaceMigration!
|
||||
@@ -3971,6 +3971,49 @@ input UpdateWorkspaceInput {
|
||||
useRecommendedModels: Boolean
|
||||
}
|
||||
|
||||
input WorkspaceMigrationInput {
|
||||
actions: [WorkspaceMigrationDeleteActionInput!]!
|
||||
}
|
||||
|
||||
input WorkspaceMigrationDeleteActionInput {
|
||||
type: WorkspaceMigrationActionType!
|
||||
metadataName: AllMetadataName!
|
||||
universalIdentifier: String!
|
||||
}
|
||||
|
||||
enum WorkspaceMigrationActionType {
|
||||
delete
|
||||
create
|
||||
update
|
||||
}
|
||||
|
||||
enum AllMetadataName {
|
||||
fieldMetadata
|
||||
objectMetadata
|
||||
view
|
||||
viewField
|
||||
viewFieldGroup
|
||||
viewGroup
|
||||
viewSort
|
||||
rowLevelPermissionPredicate
|
||||
rowLevelPermissionPredicateGroup
|
||||
viewFilterGroup
|
||||
index
|
||||
logicFunction
|
||||
viewFilter
|
||||
role
|
||||
roleTarget
|
||||
agent
|
||||
skill
|
||||
pageLayout
|
||||
pageLayoutWidget
|
||||
pageLayoutTab
|
||||
commandMenuItem
|
||||
navigationMenuItem
|
||||
frontComponent
|
||||
webhook
|
||||
}
|
||||
|
||||
input GetAuthorizationUrlForSSOInput {
|
||||
identityProviderId: UUID!
|
||||
workspaceInviteHash: String
|
||||
@@ -4118,49 +4161,6 @@ input CreateAppTokenInput {
|
||||
expiresAt: DateTime!
|
||||
}
|
||||
|
||||
input WorkspaceMigrationInput {
|
||||
actions: [WorkspaceMigrationDeleteActionInput!]!
|
||||
}
|
||||
|
||||
input WorkspaceMigrationDeleteActionInput {
|
||||
type: WorkspaceMigrationActionType!
|
||||
metadataName: AllMetadataName!
|
||||
universalIdentifier: String!
|
||||
}
|
||||
|
||||
enum WorkspaceMigrationActionType {
|
||||
delete
|
||||
create
|
||||
update
|
||||
}
|
||||
|
||||
enum AllMetadataName {
|
||||
fieldMetadata
|
||||
objectMetadata
|
||||
view
|
||||
viewField
|
||||
viewFieldGroup
|
||||
viewGroup
|
||||
viewSort
|
||||
rowLevelPermissionPredicate
|
||||
rowLevelPermissionPredicateGroup
|
||||
viewFilterGroup
|
||||
index
|
||||
logicFunction
|
||||
viewFilter
|
||||
role
|
||||
roleTarget
|
||||
agent
|
||||
skill
|
||||
pageLayout
|
||||
pageLayoutWidget
|
||||
pageLayoutTab
|
||||
commandMenuItem
|
||||
navigationMenuItem
|
||||
frontComponent
|
||||
webhook
|
||||
}
|
||||
|
||||
enum FileFolder {
|
||||
ProfilePicture
|
||||
WorkspaceLogo
|
||||
|
||||
@@ -2553,6 +2553,8 @@ export interface Query {
|
||||
currentUser: User
|
||||
currentWorkspace: Workspace
|
||||
getPublicWorkspaceDataByDomain: PublicWorkspaceData
|
||||
findManyApplications: Application[]
|
||||
findOneApplication: Application
|
||||
checkUserExists: CheckUserExist
|
||||
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid
|
||||
findWorkspaceFromInviteHash: Workspace
|
||||
@@ -2595,8 +2597,6 @@ export interface Query {
|
||||
getEmailingDomains: EmailingDomain[]
|
||||
findManyMarketplaceApps: MarketplaceApp[]
|
||||
findOneMarketplaceApp: MarketplaceApp
|
||||
findManyApplications: Application[]
|
||||
findOneApplication: Application
|
||||
__typename: 'Query'
|
||||
}
|
||||
|
||||
@@ -2725,6 +2725,10 @@ export interface Mutation {
|
||||
updateWorkspace: Workspace
|
||||
deleteCurrentWorkspace: Workspace
|
||||
checkCustomDomainValidRecords?: DomainValidRecords
|
||||
installApplication: Scalars['Boolean']
|
||||
runWorkspaceMigration: Scalars['Boolean']
|
||||
uninstallApplication: Scalars['Boolean']
|
||||
updateOneApplicationVariable: Scalars['Boolean']
|
||||
getAuthorizationUrlForSSO: GetAuthorizationUrlForSSO
|
||||
getLoginTokenFromCredentials: LoginToken
|
||||
signIn: AvailableWorkspacesAndAccessTokens
|
||||
@@ -2792,10 +2796,6 @@ export interface Mutation {
|
||||
verifyEmailingDomain: EmailingDomain
|
||||
createOneAppToken: AppToken
|
||||
installMarketplaceApp: Scalars['Boolean']
|
||||
installApplication: Scalars['Boolean']
|
||||
runWorkspaceMigration: Scalars['Boolean']
|
||||
uninstallApplication: Scalars['Boolean']
|
||||
updateOneApplicationVariable: Scalars['Boolean']
|
||||
createDevelopmentApplication: DevelopmentApplication
|
||||
generateApplicationToken: ApplicationTokenPair
|
||||
syncApplication: WorkspaceMigration
|
||||
@@ -5546,6 +5546,8 @@ export interface QueryGenqlSelection{
|
||||
currentUser?: UserGenqlSelection
|
||||
currentWorkspace?: WorkspaceGenqlSelection
|
||||
getPublicWorkspaceDataByDomain?: (PublicWorkspaceDataGenqlSelection & { __args?: {origin?: (Scalars['String'] | null)} })
|
||||
findManyApplications?: ApplicationGenqlSelection
|
||||
findOneApplication?: (ApplicationGenqlSelection & { __args?: {id?: (Scalars['UUID'] | null), universalIdentifier?: (Scalars['UUID'] | null)} })
|
||||
checkUserExists?: (CheckUserExistGenqlSelection & { __args: {email: Scalars['String'], captchaToken?: (Scalars['String'] | null)} })
|
||||
checkWorkspaceInviteHashIsValid?: (WorkspaceInviteHashValidGenqlSelection & { __args: {inviteHash: Scalars['String']} })
|
||||
findWorkspaceFromInviteHash?: (WorkspaceGenqlSelection & { __args: {inviteHash: Scalars['String']} })
|
||||
@@ -5594,8 +5596,6 @@ export interface QueryGenqlSelection{
|
||||
getEmailingDomains?: EmailingDomainGenqlSelection
|
||||
findManyMarketplaceApps?: MarketplaceAppGenqlSelection
|
||||
findOneMarketplaceApp?: (MarketplaceAppGenqlSelection & { __args: {universalIdentifier: Scalars['String']} })
|
||||
findManyApplications?: ApplicationGenqlSelection
|
||||
findOneApplication?: (ApplicationGenqlSelection & { __args?: {id?: (Scalars['UUID'] | null), universalIdentifier?: (Scalars['UUID'] | null)} })
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
@@ -5743,6 +5743,10 @@ export interface MutationGenqlSelection{
|
||||
updateWorkspace?: (WorkspaceGenqlSelection & { __args: {data: UpdateWorkspaceInput} })
|
||||
deleteCurrentWorkspace?: WorkspaceGenqlSelection
|
||||
checkCustomDomainValidRecords?: DomainValidRecordsGenqlSelection
|
||||
installApplication?: { __args: {appRegistrationId: Scalars['String'], version?: (Scalars['String'] | null)} }
|
||||
runWorkspaceMigration?: { __args: {workspaceMigration: WorkspaceMigrationInput} }
|
||||
uninstallApplication?: { __args: {universalIdentifier: Scalars['String']} }
|
||||
updateOneApplicationVariable?: { __args: {key: Scalars['String'], value: Scalars['String'], applicationId: Scalars['UUID']} }
|
||||
getAuthorizationUrlForSSO?: (GetAuthorizationUrlForSSOGenqlSelection & { __args: {input: GetAuthorizationUrlForSSOInput} })
|
||||
getLoginTokenFromCredentials?: (LoginTokenGenqlSelection & { __args: {email: Scalars['String'], password: Scalars['String'], captchaToken?: (Scalars['String'] | null), locale?: (Scalars['String'] | null), verifyEmailRedirectPath?: (Scalars['String'] | null), origin: Scalars['String']} })
|
||||
signIn?: (AvailableWorkspacesAndAccessTokensGenqlSelection & { __args: {email: Scalars['String'], password: Scalars['String'], captchaToken?: (Scalars['String'] | null), locale?: (Scalars['String'] | null), verifyEmailRedirectPath?: (Scalars['String'] | null)} })
|
||||
@@ -5810,10 +5814,6 @@ export interface MutationGenqlSelection{
|
||||
verifyEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
createOneAppToken?: (AppTokenGenqlSelection & { __args: {input: CreateOneAppTokenInput} })
|
||||
installMarketplaceApp?: { __args: {universalIdentifier: Scalars['String'], version?: (Scalars['String'] | null)} }
|
||||
installApplication?: { __args: {appRegistrationId: Scalars['String'], version?: (Scalars['String'] | null)} }
|
||||
runWorkspaceMigration?: { __args: {workspaceMigration: WorkspaceMigrationInput} }
|
||||
uninstallApplication?: { __args: {universalIdentifier: Scalars['String']} }
|
||||
updateOneApplicationVariable?: { __args: {key: Scalars['String'], value: Scalars['String'], applicationId: Scalars['UUID']} }
|
||||
createDevelopmentApplication?: (DevelopmentApplicationGenqlSelection & { __args: {universalIdentifier: Scalars['String'], name: Scalars['String']} })
|
||||
generateApplicationToken?: (ApplicationTokenPairGenqlSelection & { __args: {applicationId: Scalars['UUID']} })
|
||||
syncApplication?: (WorkspaceMigrationGenqlSelection & { __args: {manifest: Scalars['JSON']} })
|
||||
@@ -6078,6 +6078,10 @@ export interface ActivateWorkspaceInput {displayName?: (Scalars['String'] | null
|
||||
|
||||
export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),autoEnableNewAiModels?: (Scalars['Boolean'] | null),disabledAiModelIds?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]}
|
||||
|
||||
export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']}
|
||||
|
||||
export interface GetAuthorizationUrlForSSOInput {identityProviderId: Scalars['UUID'],workspaceInviteHash?: (Scalars['String'] | null)}
|
||||
|
||||
export interface CreateApplicationRegistrationInput {name: Scalars['String'],description?: (Scalars['String'] | null),logoUrl?: (Scalars['String'] | null),author?: (Scalars['String'] | null),universalIdentifier?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null),websiteUrl?: (Scalars['String'] | null),termsUrl?: (Scalars['String'] | null)}
|
||||
@@ -6126,10 +6130,6 @@ appToken: CreateAppTokenInput}
|
||||
|
||||
export interface CreateAppTokenInput {expiresAt: Scalars['DateTime']}
|
||||
|
||||
export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]}
|
||||
|
||||
export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']}
|
||||
|
||||
export interface SubscriptionGenqlSelection{
|
||||
onDbEvent?: (OnDbEventGenqlSelection & { __args: {input: OnDbEventInput} })
|
||||
onEventSubscription?: (EventSubscriptionGenqlSelection & { __args: {eventStreamId: Scalars['String']} })
|
||||
|
||||
@@ -247,6 +247,18 @@
|
||||
"nx command-no-deps -- workspace:seed:dev"
|
||||
],
|
||||
"parallel": false
|
||||
},
|
||||
"with-apps": {
|
||||
"cwd": "packages/twenty-server",
|
||||
"commands": [
|
||||
"nx ts-node-no-deps-transpile-only -- ./scripts/truncate-db.ts",
|
||||
"nx ts-node-no-deps-transpile-only -- ./scripts/setup-db.ts",
|
||||
"nx database:migrate",
|
||||
"nx command-no-deps -- cache:flush",
|
||||
"cd ../twenty-sdk && npx tsx ../twenty-apps/fixtures/build-fixture-outputs.ts",
|
||||
"nx command-no-deps -- workspace:seed:dev --with-apps"
|
||||
],
|
||||
"parallel": false
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "seed"
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { Command, CommandRunner } from 'nest-commander';
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { DevSeederService } from 'src/engine/workspace-manager/dev-seeder/services/dev-seeder.service';
|
||||
|
||||
type SeedDevOptions = {
|
||||
withApps?: boolean;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'workspace:seed:dev',
|
||||
description:
|
||||
@@ -23,10 +28,21 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
super();
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
@Option({
|
||||
flags: '--with-apps',
|
||||
description: 'Include fixture app seeding (builds and installs apps)',
|
||||
required: false,
|
||||
})
|
||||
parseWithApps(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async run(_passedParams: string[], options: SeedDevOptions): Promise<void> {
|
||||
try {
|
||||
for (const workspaceId of this.workspaceIds) {
|
||||
await this.devSeederService.seedDev(workspaceId);
|
||||
await this.devSeederService.seedDev(workspaceId, {
|
||||
includeApps: options.withApps,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
|
||||
+69
-20
@@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { promises as fs } from 'fs';
|
||||
import { join, relative } from 'path';
|
||||
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
@@ -94,6 +95,42 @@ export class ApplicationInstallService {
|
||||
);
|
||||
}
|
||||
|
||||
async installFromLocalDirectory(params: {
|
||||
appRegistrationId: string;
|
||||
extractedDir: string;
|
||||
workspaceId: string;
|
||||
}): Promise<boolean> {
|
||||
const appRegistration = await this.appRegistrationRepository.findOne({
|
||||
where: { id: params.appRegistrationId },
|
||||
});
|
||||
|
||||
if (!appRegistration) {
|
||||
throw new ApplicationException(
|
||||
`Application registration with id ${params.appRegistrationId} not found`,
|
||||
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const manifestContent = await fs.readFile(
|
||||
join(params.extractedDir, 'manifest.json'),
|
||||
'utf-8',
|
||||
);
|
||||
const manifest: Manifest = JSON.parse(manifestContent);
|
||||
|
||||
await this.installFromExtractedDir({
|
||||
appRegistration,
|
||||
extractedDir: params.extractedDir,
|
||||
manifest,
|
||||
workspaceId: params.workspaceId,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Installed app from local directory: ${appRegistration.universalIdentifier}`,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async doInstallApplication(
|
||||
appRegistration: ApplicationRegistrationEntity,
|
||||
params: { version?: string; workspaceId: string },
|
||||
@@ -111,30 +148,15 @@ export class ApplicationInstallService {
|
||||
return true;
|
||||
}
|
||||
|
||||
const universalIdentifier = appRegistration.universalIdentifier;
|
||||
|
||||
await this.ensureApplicationExists({
|
||||
universalIdentifier,
|
||||
name: resolvedPackage.manifest.application.displayName,
|
||||
workspaceId: params.workspaceId,
|
||||
applicationRegistrationId: appRegistration.id,
|
||||
sourceType: appRegistration.sourceType,
|
||||
});
|
||||
|
||||
await this.writeFilesToStorage(
|
||||
resolvedPackage.extractedDir,
|
||||
universalIdentifier,
|
||||
params.workspaceId,
|
||||
);
|
||||
|
||||
await this.applicationSyncService.synchronizeFromManifest({
|
||||
workspaceId: params.workspaceId,
|
||||
await this.installFromExtractedDir({
|
||||
appRegistration,
|
||||
extractedDir: resolvedPackage.extractedDir,
|
||||
manifest: resolvedPackage.manifest,
|
||||
applicationRegistrationId: appRegistration.id,
|
||||
workspaceId: params.workspaceId,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Successfully installed app ${universalIdentifier} v${resolvedPackage.packageJson.version ?? 'unknown'}`,
|
||||
`Successfully installed app ${appRegistration.universalIdentifier} v${resolvedPackage.packageJson.version ?? 'unknown'}`,
|
||||
);
|
||||
|
||||
return true;
|
||||
@@ -153,6 +175,33 @@ export class ApplicationInstallService {
|
||||
}
|
||||
}
|
||||
|
||||
private async installFromExtractedDir(params: {
|
||||
appRegistration: ApplicationRegistrationEntity;
|
||||
extractedDir: string;
|
||||
manifest: Manifest;
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
await this.ensureApplicationExists({
|
||||
universalIdentifier: params.appRegistration.universalIdentifier,
|
||||
name: params.manifest.application.displayName,
|
||||
workspaceId: params.workspaceId,
|
||||
applicationRegistrationId: params.appRegistration.id,
|
||||
sourceType: params.appRegistration.sourceType,
|
||||
});
|
||||
|
||||
await this.writeFilesToStorage(
|
||||
params.extractedDir,
|
||||
params.appRegistration.universalIdentifier,
|
||||
params.workspaceId,
|
||||
);
|
||||
|
||||
await this.applicationSyncService.synchronizeFromManifest({
|
||||
workspaceId: params.workspaceId,
|
||||
manifest: params.manifest,
|
||||
applicationRegistrationId: params.appRegistration.id,
|
||||
});
|
||||
}
|
||||
|
||||
private async writeFilesToStorage(
|
||||
extractedDir: string,
|
||||
applicationUniversalIdentifier: string,
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||
|
||||
// Resolve via Node module resolution, same pattern as standard-front-component-upload.service.ts
|
||||
const PACKAGES_ROOT = dirname(
|
||||
dirname(require.resolve('twenty-standard-application/package.json')),
|
||||
);
|
||||
|
||||
const FIXTURES_ROOT = join(PACKAGES_ROOT, 'twenty-apps', 'fixtures');
|
||||
|
||||
export type AppSeedDefinition = {
|
||||
registration: {
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
description: string;
|
||||
sourceType: ApplicationRegistrationSourceType;
|
||||
sourcePackage?: string;
|
||||
author?: string;
|
||||
};
|
||||
outputDir: string;
|
||||
};
|
||||
|
||||
export const APP_SEEDS: AppSeedDefinition[] = [
|
||||
{
|
||||
registration: {
|
||||
universalIdentifier: '6563e091-9f5b-4026-a3ea-7e3b3d09e218',
|
||||
name: 'Hello World',
|
||||
description: 'A sample marketplace app installed via NPM',
|
||||
sourceType: ApplicationRegistrationSourceType.NPM,
|
||||
sourcePackage: 'hello-world',
|
||||
author: 'Twenty',
|
||||
},
|
||||
outputDir: join(FIXTURES_ROOT, 'hello-world-app', '.twenty', 'output'),
|
||||
},
|
||||
{
|
||||
registration: {
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
name: 'Postcard App',
|
||||
description: 'An internal app installed as tarball',
|
||||
sourceType: ApplicationRegistrationSourceType.TARBALL,
|
||||
author: 'Twenty',
|
||||
},
|
||||
outputDir: join(FIXTURES_ROOT, 'postcard-app', '.twenty', 'output'),
|
||||
},
|
||||
{
|
||||
registration: {
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000001',
|
||||
name: 'Minimal App',
|
||||
description: 'A dev-mode app running locally',
|
||||
sourceType: ApplicationRegistrationSourceType.LOCAL,
|
||||
},
|
||||
outputDir: join(FIXTURES_ROOT, 'minimal-app', '.twenty', 'output'),
|
||||
},
|
||||
];
|
||||
@@ -3,6 +3,8 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
|
||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
@@ -42,7 +44,12 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
ApplicationModule,
|
||||
FeatureFlagModule,
|
||||
FileStorageModule,
|
||||
TypeOrmModule.forFeature([WorkspaceEntity, ObjectMetadataEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
ObjectMetadataEntity,
|
||||
ApplicationRegistrationEntity,
|
||||
]),
|
||||
ApplicationInstallModule,
|
||||
ObjectPermissionModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceCacheModule,
|
||||
|
||||
+74
-4
@@ -1,10 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
@@ -14,6 +16,10 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import {
|
||||
APP_SEEDS,
|
||||
type AppSeedDefinition,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/app-seeds.constant';
|
||||
import { SeededWorkspacesIds } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { DevSeederPermissionsService } from 'src/engine/workspace-manager/dev-seeder/core/services/dev-seeder-permissions.service';
|
||||
import { seedCoreSchema } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-core-schema.util';
|
||||
@@ -27,6 +33,8 @@ import { TwentyStandardApplicationService } from 'src/engine/workspace-manager/t
|
||||
|
||||
@Injectable()
|
||||
export class DevSeederService {
|
||||
private readonly logger = new Logger(DevSeederService.name);
|
||||
|
||||
constructor(
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
@@ -37,12 +45,18 @@ export class DevSeederService {
|
||||
private readonly devSeederPermissionsService: DevSeederPermissionsService,
|
||||
private readonly devSeederDataService: DevSeederDataService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly applicationInstallService: ApplicationInstallService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
@InjectRepository(ApplicationRegistrationEntity)
|
||||
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {}
|
||||
|
||||
public async seedDev(workspaceId: SeededWorkspacesIds): Promise<void> {
|
||||
public async seedDev(
|
||||
workspaceId: SeededWorkspacesIds,
|
||||
options: { includeApps?: boolean } = {},
|
||||
): Promise<void> {
|
||||
const isBillingEnabled = this.twentyConfigService.get('IS_BILLING_ENABLED');
|
||||
const appVersion = this.twentyConfigService.get('APP_VERSION');
|
||||
|
||||
@@ -83,6 +97,10 @@ export class DevSeederService {
|
||||
},
|
||||
);
|
||||
|
||||
if (options.includeApps) {
|
||||
await this.seedApps(workspaceId);
|
||||
}
|
||||
|
||||
await this.devSeederMetadataService.seed({
|
||||
dataSourceMetadata,
|
||||
workspaceId,
|
||||
@@ -166,4 +184,56 @@ export class DevSeederService {
|
||||
|
||||
await this.workspaceCacheStorageService.flush(workspaceId, undefined);
|
||||
}
|
||||
|
||||
private async seedApps(workspaceId: string): Promise<void> {
|
||||
for (const seed of APP_SEEDS) {
|
||||
try {
|
||||
await this.seedOneApp(seed, workspaceId);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to seed app "${seed.registration.name}": ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async seedOneApp(
|
||||
seed: AppSeedDefinition,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
let registration = await this.appRegistrationRepository.findOne({
|
||||
where: {
|
||||
universalIdentifier: seed.registration.universalIdentifier,
|
||||
},
|
||||
});
|
||||
|
||||
if (!registration) {
|
||||
registration = await this.appRegistrationRepository.save(
|
||||
this.appRegistrationRepository.create({
|
||||
universalIdentifier: seed.registration.universalIdentifier,
|
||||
name: seed.registration.name,
|
||||
description: seed.registration.description,
|
||||
sourceType: seed.registration.sourceType,
|
||||
sourcePackage: seed.registration.sourcePackage,
|
||||
author: seed.registration.author,
|
||||
oAuthClientId: `20202020-seed-${seed.registration.universalIdentifier.slice(0, 8)}`,
|
||||
oAuthClientSecretHash: 'seed',
|
||||
oAuthRedirectUris: [],
|
||||
oAuthScopes: [],
|
||||
ownerWorkspaceId: workspaceId,
|
||||
createdByUserId: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await this.applicationInstallService.installFromLocalDirectory({
|
||||
appRegistrationId: registration.id,
|
||||
extractedDir: seed.outputDir,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Seeded app "${seed.registration.name}" (${seed.registration.sourceType})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user