From 120096346a18f776fe319c18aa604322db6e7d35 Mon Sep 17 00:00:00 2001 From: martmull Date: Thu, 26 Feb 2026 11:09:21 +0100 Subject: [PATCH] Add define post isntall logic function (#18248) As title --- packages/create-twenty-app/README.md | 4 + packages/create-twenty-app/package.json | 2 +- .../src/constants/base-application/LLMS.md | 3 + .../src/utils/__tests__/app-template.spec.ts | 144 ++++++++++++++++++ .../src/utils/app-template.ts | 50 ++++-- .../developers/extend/capabilities/apps.mdx | 83 +++++++--- packages/twenty-sdk/README.md | 6 +- packages/twenty-sdk/package.json | 2 +- .../build/manifest/manifest-build.ts | 48 ++++++ .../build/manifest/manifest-extract-config.ts | 6 + .../src/sdk/application/application-config.ts | 5 +- packages/twenty-sdk/src/sdk/index.ts | 6 + ...define-post-install-logic-function.spec.ts | 76 +++++++++ .../define-pre-install-logic-function.spec.ts | 76 +++++++++ .../define-post-install-logic-function.ts | 36 +++++ .../define-pre-install-logic-function.ts | 36 +++++ .../install-logic-function-payload-type.ts | 7 + ...data-names-synced-with-labels.util.spec.ts | 4 +- ...-input-validation.integration-spec.ts.snap | 2 +- ...enu-item-creation.integration-spec.ts.snap | 2 +- ...enu-item-deletion.integration-spec.ts.snap | 30 ++-- ...-menu-item-update.integration-spec.ts.snap | 2 +- ...enu-item-creation.integration-spec.ts.snap | 5 +- ...enu-item-deletion.integration-spec.ts.snap | 2 +- ...-menu-item-update.integration-spec.ts.snap | 4 +- ...e-object-metadata.integration-spec.ts.snap | 2 +- ...-predicate-upsert.integration-spec.ts.snap | 8 +- ...er-group-creation.integration-spec.ts.snap | 2 +- .../src/application/applicationType.ts | 3 +- 29 files changed, 585 insertions(+), 71 deletions(-) create mode 100644 packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-post-install-logic-function.spec.ts create mode 100644 packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-pre-install-logic-function.spec.ts create mode 100644 packages/twenty-sdk/src/sdk/logic-functions/define-post-install-logic-function.ts create mode 100644 packages/twenty-sdk/src/sdk/logic-functions/define-pre-install-logic-function.ts create mode 100644 packages/twenty-sdk/src/sdk/logic-functions/install-logic-function-payload-type.ts diff --git a/packages/create-twenty-app/README.md b/packages/create-twenty-app/README.md index 9ca434895b..9217197e77 100644 --- a/packages/create-twenty-app/README.md +++ b/packages/create-twenty-app/README.md @@ -50,6 +50,9 @@ yarn twenty function:logs # Execute a function with a JSON payload yarn twenty function:execute -n my-function -p '{"key": "value"}' +# Execute the pre-install function +yarn twenty function:execute --preInstall + # Execute the post-install function yarn twenty function:execute --postInstall @@ -92,6 +95,7 @@ In interactive mode, you can pick from: **Core files (always created):** - `application-config.ts` — Application metadata configuration - `roles/default-role.ts` — Default role for logic functions +- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation) - `logic-functions/post-install.ts` — Post-install logic function (runs after app installation) - TypeScript configuration, ESLint, package.json, .gitignore - A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk diff --git a/packages/create-twenty-app/package.json b/packages/create-twenty-app/package.json index 35884ef445..20bc619f0c 100644 --- a/packages/create-twenty-app/package.json +++ b/packages/create-twenty-app/package.json @@ -1,6 +1,6 @@ { "name": "create-twenty-app", - "version": "0.6.1", + "version": "0.6.2", "description": "Command-line interface to create Twenty application", "main": "dist/cli.cjs", "bin": "dist/cli.cjs", diff --git a/packages/create-twenty-app/src/constants/base-application/LLMS.md b/packages/create-twenty-app/src/constants/base-application/LLMS.md index 421003481f..38afd91745 100644 --- a/packages/create-twenty-app/src/constants/base-application/LLMS.md +++ b/packages/create-twenty-app/src/constants/base-application/LLMS.md @@ -3,6 +3,9 @@ - Documentation: https://docs.twenty.com/developers/extend/capabilities/apps - Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app +## UUID requirement +- All generated UUIDs must be valid UUID v4. + ## Common Pitfalls - Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it. diff --git a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts index 78bbb8115b..2a0b9a275f 100644 --- a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts +++ b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts @@ -375,6 +375,18 @@ describe('copyBaseApplicationProject', () => { ), ), ).toBe(true); + + // Install functions should always exist + expect( + await fs.pathExists( + join(srcPath, 'logic-functions', 'pre-install.ts'), + ), + ).toBe(true); + expect( + await fs.pathExists( + join(srcPath, 'logic-functions', 'post-install.ts'), + ), + ).toBe(true); }); }); @@ -398,6 +410,18 @@ describe('copyBaseApplicationProject', () => { await fs.pathExists(join(srcPath, 'roles', DEFAULT_ROLE_FILE_NAME)), ).toBe(true); + // Install functions should always exist (not gated by exampleOptions) + expect( + await fs.pathExists( + join(srcPath, 'logic-functions', 'pre-install.ts'), + ), + ).toBe(true); + expect( + await fs.pathExists( + join(srcPath, 'logic-functions', 'post-install.ts'), + ), + ).toBe(true); + // Example files should not exist expect( await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')), @@ -676,4 +700,124 @@ describe('copyBaseApplicationProject', () => { expect(content).toContain('position: 0'); }); }); + + describe('pre-install logic function', () => { + it('should create pre-install.ts with definePreInstallLogicFunction and typed payload', async () => { + await copyBaseApplicationProject({ + appName: 'my-test-app', + appDisplayName: 'My Test App', + appDescription: 'A test application', + appDirectory: testAppDirectory, + exampleOptions: NO_EXAMPLES, + }); + + const preInstallPath = join( + testAppDirectory, + 'src', + 'logic-functions', + 'pre-install.ts', + ); + + expect(await fs.pathExists(preInstallPath)).toBe(true); + + const content = await fs.readFile(preInstallPath, 'utf8'); + + expect(content).toContain( + "import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'", + ); + expect(content).toContain( + 'export default definePreInstallLogicFunction({', + ); + expect(content).toContain("name: 'pre-install'"); + expect(content).toContain('timeoutSeconds: 300'); + expect(content).toContain( + 'const handler = async (payload: InstallLogicFunctionPayload): Promise', + ); + expect(content).toContain('payload.previousVersion'); + + // Verify it has a universalIdentifier (UUID format) + expect(content).toMatch( + /universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/, + ); + }); + + it('should always create pre-install.ts regardless of example options', async () => { + await copyBaseApplicationProject({ + appName: 'my-test-app', + appDisplayName: 'My Test App', + appDescription: 'A test application', + appDirectory: testAppDirectory, + exampleOptions: NO_EXAMPLES, + }); + + const preInstallPath = join( + testAppDirectory, + 'src', + 'logic-functions', + 'pre-install.ts', + ); + + expect(await fs.pathExists(preInstallPath)).toBe(true); + }); + }); + + describe('post-install logic function', () => { + it('should create post-install.ts with definePostInstallLogicFunction and typed payload', async () => { + await copyBaseApplicationProject({ + appName: 'my-test-app', + appDisplayName: 'My Test App', + appDescription: 'A test application', + appDirectory: testAppDirectory, + exampleOptions: NO_EXAMPLES, + }); + + const postInstallPath = join( + testAppDirectory, + 'src', + 'logic-functions', + 'post-install.ts', + ); + + expect(await fs.pathExists(postInstallPath)).toBe(true); + + const content = await fs.readFile(postInstallPath, 'utf8'); + + expect(content).toContain( + "import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'", + ); + expect(content).toContain( + 'export default definePostInstallLogicFunction({', + ); + expect(content).toContain("name: 'post-install'"); + expect(content).toContain('timeoutSeconds: 300'); + expect(content).toContain( + 'const handler = async (payload: InstallLogicFunctionPayload): Promise', + ); + expect(content).toContain('payload.previousVersion'); + + // Verify it has a universalIdentifier (UUID format) + expect(content).toMatch( + /universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/, + ); + }); + + it('should always create post-install.ts regardless of example options', async () => { + await copyBaseApplicationProject({ + appName: 'my-test-app', + appDisplayName: 'My Test App', + appDescription: 'A test application', + appDirectory: testAppDirectory, + exampleOptions: NO_EXAMPLES, + }); + + const postInstallPath = join( + testAppDirectory, + 'src', + 'logic-functions', + 'post-install.ts', + ); + + expect(await fs.pathExists(postInstallPath)).toBe(true); + }); + }); }); diff --git a/packages/create-twenty-app/src/utils/app-template.ts b/packages/create-twenty-app/src/utils/app-template.ts index a1afca9bfb..5d6abd63c7 100644 --- a/packages/create-twenty-app/src/utils/app-template.ts +++ b/packages/create-twenty-app/src/utils/app-template.ts @@ -97,6 +97,12 @@ export const copyBaseApplicationProject = async ({ }); } + await createDefaultPreInstallFunction({ + appDirectory: sourceFolderPath, + fileFolder: 'logic-functions', + fileName: 'pre-install.ts', + }); + await createDefaultPostInstallFunction({ appDirectory: sourceFolderPath, fileFolder: 'logic-functions', @@ -268,6 +274,36 @@ export default defineLogicFunction({ await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content); }; +const createDefaultPreInstallFunction = async ({ + appDirectory, + fileFolder, + fileName, +}: { + appDirectory: string; + fileFolder?: string; + fileName: string; +}) => { + const universalIdentifier = v4(); + + const content = `import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: '${universalIdentifier}', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +`; + + await fs.ensureDir(join(appDirectory, fileFolder ?? '')); + await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content); +}; + const createDefaultPostInstallFunction = async ({ appDirectory, fileFolder, @@ -279,16 +315,14 @@ const createDefaultPostInstallFunction = async ({ }) => { const universalIdentifier = v4(); - const content = `import { defineLogicFunction } from 'twenty-sdk'; + const content = `import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; -export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '${universalIdentifier}'; - -const handler = async (): Promise => { - console.log('Post install logic function executed successfully!'); +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); }; -export default defineLogicFunction({ - universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER, +export default definePostInstallLogicFunction({ + universalIdentifier: '${universalIdentifier}', name: 'post-install', description: 'Runs after installation to set up the application.', timeoutSeconds: 300, @@ -477,14 +511,12 @@ const createApplicationConfig = async ({ }) => { const content = `import { defineApplication } from 'twenty-sdk'; import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; -import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install'; export default defineApplication({ universalIdentifier: '${v4()}', displayName: '${displayName}', description: '${description ?? ''}', defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER, }); `; diff --git a/packages/twenty-docs/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/developers/extend/capabilities/apps.mdx index fdd75133e0..3ba478800e 100644 --- a/packages/twenty-docs/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/developers/extend/capabilities/apps.mdx @@ -60,6 +60,9 @@ yarn twenty function:logs # Execute a function by name yarn twenty function:execute -n my-function -p '{"name": "test"}' +# Execute the pre-install function +yarn twenty function:execute --preInstall + # Execute the post-install function yarn twenty function:execute --postInstall @@ -79,7 +82,7 @@ When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder: - Copies a minimal base application into `my-twenty-app/` - Adds a local `twenty-sdk` dependency and Yarn 4 configuration - Creates config files and scripts wired to the `twenty` CLI -- Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode +- Generates core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode A freshly scaffolded app with the default `--exhaustive` mode looks like this: @@ -106,6 +109,7 @@ my-twenty-app/ │ └── example-field.ts # Example standalone field definition ├── logic-functions/ │ ├── hello-world.ts # Example logic function + │ ├── pre-install.ts # Pre-install logic function │ └── post-install.ts # Post-install logic function ├── front-components/ │ └── hello-world.tsx # Example front component @@ -117,7 +121,7 @@ my-twenty-app/ └── example-skill.ts # Example AI agent skill definition ``` -With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include. +With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include. At a high level: @@ -138,6 +142,8 @@ The SDK detects entities by parsing your TypeScript files for **`export default |-----------------|-------------| | `defineObject()` | Custom object definitions | | `defineLogicFunction()` | Logic function definitions | +| `definePreInstallLogicFunction()` | Pre-install logic function (runs before installation) | +| `definePostInstallLogicFunction()` | Post-install logic function (runs after installation) | | `defineFrontComponent()` | Front component definitions | | `defineRole()` | Role definitions | | `defineField()` | Field extensions for existing objects | @@ -212,6 +218,8 @@ The SDK provides helper functions for defining your app entities. As described i | `defineApplication()` | Configure application metadata (required, one per app) | | `defineObject()` | Define custom objects with fields | | `defineLogicFunction()` | Define logic functions with handlers | +| `definePreInstallLogicFunction()` | Define a pre-install logic function (one per app) | +| `definePostInstallLogicFunction()` | Define a post-install logic function (one per app) | | `defineFrontComponent()` | Define front components for custom UI | | `defineRole()` | Configure role permissions and object access | | `defineField()` | Extend existing objects with additional fields | @@ -318,6 +326,7 @@ Every app has a single `application-config.ts` file that describes: - **Who the app is**: identifiers, display name, and description. - **How its functions run**: which role they use for permissions. - **(Optional) variables**: key–value pairs exposed to your functions as environment variables. +- **(Optional) pre-install function**: a logic function that runs before the app is installed. - **(Optional) post-install function**: a logic function that runs after the app is installed. Use `defineApplication()` to define your application configuration: @@ -326,7 +335,6 @@ Use `defineApplication()` to define your application configuration: // src/application-config.ts import { defineApplication } from 'twenty-sdk'; import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; -import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install'; export default defineApplication({ universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', @@ -342,7 +350,6 @@ export default defineApplication({ }, }, defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER, }); ``` @@ -350,7 +357,7 @@ Notes: - `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs. - `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`). - `defaultRoleUniversalIdentifier` must match the role file (see below). -- `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions). +- Pre-install and post-install functions are automatically detected during the manifest build. See [Pre-install functions](#pre-install-functions) and [Post-install functions](#post-install-functions). #### Roles and permissions @@ -483,6 +490,43 @@ Notes: - The `triggers` array is optional. Functions without triggers can be used as utility functions called by other functions. - You can mix multiple trigger types in a single function. +### Pre-install functions + +A pre-install function is a logic function that runs automatically before your app is installed on a workspace. This is useful for validation tasks, prerequisite checks, or preparing workspace state before the main installation proceeds. + +When you scaffold a new app with `create-twenty-app`, a pre-install function is generated for you at `src/logic-functions/pre-install.ts`: + +```typescript +// src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: '', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +You can also manually execute the pre-install function at any time using the CLI: + +```bash filename="Terminal" +yarn twenty function:execute --preInstall +``` + +Key points: +- Pre-install functions use `definePreInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +- The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs). +- Only one pre-install function is allowed per application. The manifest build will error if more than one is detected. +- The function's `universalIdentifier` is automatically set as `preInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`. +- The default timeout is set to 300 seconds (5 minutes) to allow for longer preparation tasks. +- Pre-install functions do not need triggers — they are invoked by the platform before installation or manually via `function:execute --preInstall`. + ### Post-install functions A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings. @@ -491,16 +535,14 @@ When you scaffold a new app with `create-twenty-app`, a post-install function is ```typescript // src/logic-functions/post-install.ts -import { defineLogicFunction } from 'twenty-sdk'; +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; -export const POST_INSTALL_UNIVERSAL_IDENTIFIER = ''; - -const handler = async (): Promise => { - console.log('Post install logic function executed successfully!'); +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); }; -export default defineLogicFunction({ - universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER, +export default definePostInstallLogicFunction({ + universalIdentifier: '', name: 'post-install', description: 'Runs after installation to set up the application.', timeoutSeconds: 300, @@ -508,17 +550,6 @@ export default defineLogicFunction({ }); ``` -The function is wired into your app by referencing its universal identifier in `application-config.ts`: - -```typescript -import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install'; - -export default defineApplication({ - // ... - postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER, -}); -``` - You can also manually execute the post-install function at any time using the CLI: ```bash filename="Terminal" @@ -526,8 +557,10 @@ yarn twenty function:execute --postInstall ``` Key points: -- Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function. -- The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation. +- Post-install functions use `definePostInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +- The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs). +- Only one post-install function is allowed per application. The manifest build will error if more than one is detected. +- The function's `universalIdentifier` is automatically set as `postInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`. - The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding. - Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`. diff --git a/packages/twenty-sdk/README.md b/packages/twenty-sdk/README.md index c016ad60c6..c6fffb93b4 100644 --- a/packages/twenty-sdk/README.md +++ b/packages/twenty-sdk/README.md @@ -155,7 +155,8 @@ Application development commands. - `twenty function:execute [appPath]` — Execute a logic function with a JSON payload. - Options: - - `--postInstall`: Execute the post-install logic function defined in the application config (required if `-n` and `-u` not provided). + - `--preInstall`: Execute the pre-install logic function defined in the application manifest (required if `--postInstall`, `-n`, and `-u` not provided). + - `--postInstall`: Execute the post-install logic function defined in the application manifest (required if `--preInstall`, `-n`, and `-u` not provided). - `-n, --functionName `: Name of the function to execute (required if `--postInstall` and `-u` not provided). - `-u, --functionUniversalIdentifier `: Universal ID of the function to execute (required if `--postInstall` and `-n` not provided). - `-p, --payload `: JSON payload to send to the function (default: `{}`). @@ -208,6 +209,9 @@ twenty function:execute -n my-function -p '{"name": "test"}' # Execute a function by universal identifier twenty function:execute -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -p '{"key": "value"}' +# Execute the pre-install function +twenty function:execute --preInstall + # Execute the post-install function twenty function:execute --postInstall ``` diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index ec426ea90d..36cca295de 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -1,6 +1,6 @@ { "name": "twenty-sdk", - "version": "0.6.1", + "version": "0.6.2", "main": "dist/index.cjs", "module": "dist/index.mjs", "types": "dist/sdk/index.d.ts", diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts index 1ce70842b7..16b79bae55 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts @@ -3,6 +3,7 @@ import { extractDefineEntity, ManifestEntityKey, TARGET_FUNCTION_TO_ENTITY_KEY_MAPPING, + TargetFunction, } from '@/cli/utilities/build/manifest/manifest-extract-config'; import { extractManifestFromFile } from '@/cli/utilities/build/manifest/manifest-extract-config-from-file'; import { @@ -72,6 +73,8 @@ export const buildManifest = async ( const views: ViewManifest[] = []; const navigationMenuItems: NavigationMenuItemManifest[] = []; const pageLayouts: PageLayoutManifest[] = []; + const preInstallLogicFunctionUniversalIdentifiers: string[] = []; + const postInstallLogicFunctionUniversalIdentifiers: string[] = []; const applicationFilePaths: string[] = []; const objectsFilePaths: string[] = []; @@ -207,6 +210,23 @@ export const buildManifest = async ( logicFunctions.push(config); logicFunctionsFilePaths.push(relativePath); + + if ( + targetFunctionName === TargetFunction.DefinePreInstallLogicFunction + ) { + preInstallLogicFunctionUniversalIdentifiers.push( + extract.config.universalIdentifier, + ); + } + + if ( + targetFunctionName === TargetFunction.DefinePostInstallLogicFunction + ) { + postInstallLogicFunctionUniversalIdentifiers.push( + extract.config.universalIdentifier, + ); + } + break; } case ManifestEntityKey.FrontComponents: { @@ -304,6 +324,34 @@ export const buildManifest = async ( ); } + if (preInstallLogicFunctionUniversalIdentifiers.length > 1) { + errors.push( + 'Only one pre install logic function is allowed per application', + ); + } + + if (postInstallLogicFunctionUniversalIdentifiers.length > 1) { + errors.push( + 'Only one post install logic function is allowed per application', + ); + } + + if (application && preInstallLogicFunctionUniversalIdentifiers.length >= 1) { + application = { + ...application, + preInstallLogicFunctionUniversalIdentifier: + preInstallLogicFunctionUniversalIdentifiers[0], + }; + } + + if (application && postInstallLogicFunctionUniversalIdentifiers.length >= 1) { + application = { + ...application, + postInstallLogicFunctionUniversalIdentifier: + postInstallLogicFunctionUniversalIdentifiers[0], + }; + } + const manifest = !application ? null : { diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts index 3ff34fe6d5..29be4e4c22 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-extract-config.ts @@ -4,6 +4,8 @@ export enum TargetFunction { DefineApplication = 'defineApplication', DefineField = 'defineField', DefineLogicFunction = 'defineLogicFunction', + DefinePreInstallLogicFunction = 'definePreInstallLogicFunction', + DefinePostInstallLogicFunction = 'definePostInstallLogicFunction', DefineObject = 'defineObject', DefineRole = 'defineRole', DefineSkill = 'defineSkill', @@ -36,6 +38,10 @@ export const TARGET_FUNCTION_TO_ENTITY_KEY_MAPPING: Record< [TargetFunction.DefineApplication]: ManifestEntityKey.Application, [TargetFunction.DefineField]: ManifestEntityKey.Fields, [TargetFunction.DefineLogicFunction]: ManifestEntityKey.LogicFunctions, + [TargetFunction.DefinePreInstallLogicFunction]: + ManifestEntityKey.LogicFunctions, + [TargetFunction.DefinePostInstallLogicFunction]: + ManifestEntityKey.LogicFunctions, [TargetFunction.DefineObject]: ManifestEntityKey.Objects, [TargetFunction.DefineRole]: ManifestEntityKey.Roles, [TargetFunction.DefineSkill]: ManifestEntityKey.Skills, diff --git a/packages/twenty-sdk/src/sdk/application/application-config.ts b/packages/twenty-sdk/src/sdk/application/application-config.ts index e7884ca638..33b295cdad 100644 --- a/packages/twenty-sdk/src/sdk/application/application-config.ts +++ b/packages/twenty-sdk/src/sdk/application/application-config.ts @@ -2,5 +2,8 @@ import { type ApplicationManifest } from 'twenty-shared/application'; export type ApplicationConfig = Omit< ApplicationManifest, - 'packageJsonChecksum' | 'yarnLockChecksum' | 'apiClientChecksum' + | 'packageJsonChecksum' + | 'yarnLockChecksum' + | 'apiClientChecksum' + | 'postInstallLogicFunctionUniversalIdentifier' >; diff --git a/packages/twenty-sdk/src/sdk/index.ts b/packages/twenty-sdk/src/sdk/index.ts index e4f7382472..a00d869579 100644 --- a/packages/twenty-sdk/src/sdk/index.ts +++ b/packages/twenty-sdk/src/sdk/index.ts @@ -29,6 +29,12 @@ export type { FrontComponentType, } from './front-component-config'; export { defineLogicFunction } from './logic-functions/define-logic-function'; +export type { + InstallLogicFunctionPayload, + InstallLogicFunctionHandler, +} from './logic-functions/install-logic-function-payload-type'; +export { definePreInstallLogicFunction } from './logic-functions/define-pre-install-logic-function'; +export { definePostInstallLogicFunction } from './logic-functions/define-post-install-logic-function'; export type { LogicFunctionConfig, LogicFunctionHandler, diff --git a/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-post-install-logic-function.spec.ts b/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-post-install-logic-function.spec.ts new file mode 100644 index 0000000000..9f517ab892 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-post-install-logic-function.spec.ts @@ -0,0 +1,76 @@ +import { definePostInstallLogicFunction } from '@/sdk/logic-functions/define-post-install-logic-function'; +import { type InstallLogicFunctionPayload } from '@/sdk/logic-functions/install-logic-function-payload-type'; + +const mockHandler = async (payload: InstallLogicFunctionPayload) => ({ + success: true, + previousVersion: payload.previousVersion, +}); + +describe('definePostInstallLogicFunction', () => { + const validRouteConfig = { + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'Send Postcard', + handler: mockHandler, + }; + + it('should return the config when valid with route trigger', () => { + const result = definePostInstallLogicFunction(validRouteConfig); + + expect(result.config).toEqual(validRouteConfig); + }); + + it('should pass through optional fields', () => { + const config = { + ...validRouteConfig, + description: 'Send a postcard to a contact', + timeoutSeconds: 30, + }; + + const result = definePostInstallLogicFunction(config as any); + + expect(result.config.description).toBe('Send a postcard to a contact'); + expect(result.config.timeoutSeconds).toBe(30); + }); + + it('should return error when universalIdentifier is missing', () => { + const config = { + name: 'Send Postcard', + handler: mockHandler, + }; + const result = definePostInstallLogicFunction(config as any); + + expect(result.success).toBe(false); + expect(result.errors).toContain( + 'Post install logic function must have a universalIdentifier', + ); + }); + + it('should return error when handler is missing', () => { + const config = { + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'Send Postcard', + }; + + const result = definePostInstallLogicFunction(config as any); + + expect(result.success).toBe(false); + expect(result.errors).toContain( + 'Post install logic function must have a handler', + ); + }); + + it('should return error when handler is not a function', () => { + const config = { + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'Send Postcard', + handler: 'not-a-function', + }; + + const result = definePostInstallLogicFunction(config as any); + + expect(result.success).toBe(false); + expect(result.errors).toContain( + 'Post install logic function handler must be a function', + ); + }); +}); diff --git a/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-pre-install-logic-function.spec.ts b/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-pre-install-logic-function.spec.ts new file mode 100644 index 0000000000..30eb5d89bb --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-functions/__tests__/define-pre-install-logic-function.spec.ts @@ -0,0 +1,76 @@ +import { definePreInstallLogicFunction } from '@/sdk/logic-functions/define-pre-install-logic-function'; +import { type InstallLogicFunctionPayload } from '@/sdk/logic-functions/install-logic-function-payload-type'; + +const mockHandler = async (payload: InstallLogicFunctionPayload) => ({ + success: true, + previousVersion: payload.previousVersion, +}); + +describe('definePreInstallLogicFunction', () => { + const validRouteConfig = { + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'Send Postcard', + handler: mockHandler, + }; + + it('should return the config when valid with route trigger', () => { + const result = definePreInstallLogicFunction(validRouteConfig); + + expect(result.config).toEqual(validRouteConfig); + }); + + it('should pass through optional fields', () => { + const config = { + ...validRouteConfig, + description: 'Send a postcard to a contact', + timeoutSeconds: 30, + }; + + const result = definePreInstallLogicFunction(config as any); + + expect(result.config.description).toBe('Send a postcard to a contact'); + expect(result.config.timeoutSeconds).toBe(30); + }); + + it('should return error when universalIdentifier is missing', () => { + const config = { + name: 'Send Postcard', + handler: mockHandler, + }; + const result = definePreInstallLogicFunction(config as any); + + expect(result.success).toBe(false); + expect(result.errors).toContain( + 'Pre install logic function must have a universalIdentifier', + ); + }); + + it('should return error when handler is missing', () => { + const config = { + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'Send Postcard', + }; + + const result = definePreInstallLogicFunction(config as any); + + expect(result.success).toBe(false); + expect(result.errors).toContain( + 'Pre install logic function must have a handler', + ); + }); + + it('should return error when handler is not a function', () => { + const config = { + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'Send Postcard', + handler: 'not-a-function', + }; + + const result = definePreInstallLogicFunction(config as any); + + expect(result.success).toBe(false); + expect(result.errors).toContain( + 'Pre install logic function handler must be a function', + ); + }); +}); diff --git a/packages/twenty-sdk/src/sdk/logic-functions/define-post-install-logic-function.ts b/packages/twenty-sdk/src/sdk/logic-functions/define-post-install-logic-function.ts new file mode 100644 index 0000000000..2359e2fc43 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-functions/define-post-install-logic-function.ts @@ -0,0 +1,36 @@ +import { type LogicFunctionConfig } from '@/sdk/logic-functions/logic-function-config'; +import { type InstallLogicFunctionHandler } from '@/sdk/logic-functions/install-logic-function-payload-type'; +import { createValidationResult } from '@/sdk/common/utils/create-validation-result'; +import type { DefineEntity } from '@/sdk/common/types/define-entity.type'; + +export const definePostInstallLogicFunction: DefineEntity< + Omit< + LogicFunctionConfig, + | 'cronTriggerSettings' + | 'databaseEventTriggerSettings' + | 'httpRouteTriggerSettings' + | 'isTool' + | 'handler' + > & { + handler: InstallLogicFunctionHandler; + } +> = (config) => { + const errors = []; + + if (!config.universalIdentifier) { + errors.push('Post install logic function must have a universalIdentifier'); + } + + if (!config.handler) { + errors.push('Post install logic function must have a handler'); + } + + if (typeof config.handler !== 'function') { + errors.push('Post install logic function handler must be a function'); + } + + return createValidationResult({ + config, + errors, + }); +}; diff --git a/packages/twenty-sdk/src/sdk/logic-functions/define-pre-install-logic-function.ts b/packages/twenty-sdk/src/sdk/logic-functions/define-pre-install-logic-function.ts new file mode 100644 index 0000000000..8d8d917238 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-functions/define-pre-install-logic-function.ts @@ -0,0 +1,36 @@ +import { type LogicFunctionConfig } from '@/sdk/logic-functions/logic-function-config'; +import { type InstallLogicFunctionHandler } from '@/sdk/logic-functions/install-logic-function-payload-type'; +import { createValidationResult } from '@/sdk/common/utils/create-validation-result'; +import type { DefineEntity } from '@/sdk/common/types/define-entity.type'; + +export const definePreInstallLogicFunction: DefineEntity< + Omit< + LogicFunctionConfig, + | 'cronTriggerSettings' + | 'databaseEventTriggerSettings' + | 'httpRouteTriggerSettings' + | 'isTool' + | 'handler' + > & { + handler: InstallLogicFunctionHandler; + } +> = (config) => { + const errors = []; + + if (!config.universalIdentifier) { + errors.push('Pre install logic function must have a universalIdentifier'); + } + + if (!config.handler) { + errors.push('Pre install logic function must have a handler'); + } + + if (typeof config.handler !== 'function') { + errors.push('Pre install logic function handler must be a function'); + } + + return createValidationResult({ + config, + errors, + }); +}; diff --git a/packages/twenty-sdk/src/sdk/logic-functions/install-logic-function-payload-type.ts b/packages/twenty-sdk/src/sdk/logic-functions/install-logic-function-payload-type.ts new file mode 100644 index 0000000000..7fb6e48a22 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-functions/install-logic-function-payload-type.ts @@ -0,0 +1,7 @@ +export type InstallLogicFunctionPayload = { + previousVersion: string; +}; + +export type InstallLogicFunctionHandler = ( + payload: InstallLogicFunctionPayload, +) => any | Promise; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/__tests__/are-flat-object-metadata-names-synced-with-labels.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/__tests__/are-flat-object-metadata-names-synced-with-labels.util.spec.ts index 36ca582135..c22ed1c2cf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/__tests__/are-flat-object-metadata-names-synced-with-labels.util.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/__tests__/are-flat-object-metadata-names-synced-with-labels.util.spec.ts @@ -130,8 +130,8 @@ describe('areFlatObjectMetadataNamesSyncedWithLabels', () => { it('should return true with complex labels', () => { const result = areFlatObjectMetadataNamesSyncedWithLabels({ flatObjectMetadata: { - nameSingular: 'wrongCreatedAtObject', - namePlural: 'wrongCreatedAtObjects', + nameSingular: 'wrongCreatedatObject', + namePlural: 'wrongCreatedatObjects', labelSingular: 'Wrong CreatedAt Object', labelPlural: 'Wrong CreatedAt Objects', }, diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/uuid-field-create-input-validation.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/uuid-field-create-input-validation.integration-spec.ts.snap index 42302ec38f..4b5d55887a 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/uuid-field-create-input-validation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/__snapshots__/uuid-field-create-input-validation.integration-spec.ts.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`Create input validation - UUID Gql create input - failure UUID - should fail with : {"uuidField":"non-uuid"} 1`] = `"Invalid UUID"`; +exports[`Create input validation - UUID Gql create input - failure UUID - should fail with : {"uuidField":"non-uuid"} 1`] = `"Invalid UUID: 'non-uuid'"`; exports[`Create input validation - UUID Rest create input - failure UUID - should fail with : {"uuidField":"non-uuid"} 1`] = `"["Invalid UUID value 'non-uuid' for field \\"uuidField\\""]"`; diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap index 50fbf11e7a..f3239151f9 100644 --- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-creation.integration-spec.ts.snap @@ -57,7 +57,7 @@ exports[`CommandMenuItem creation should fail when creating with empty workflowV "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-deletion.integration-spec.ts.snap index 6abadb74d4..825c14ee75 100644 --- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-deletion.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-deletion.integration-spec.ts.snap @@ -12,20 +12,6 @@ exports[`CommandMenuItem deletion should fail when deleting a non-existent comma } `; -exports[`CommandMenuItem deletion should fail when deleting with missing id 1`] = ` -{ - "extensions": { - "code": "BAD_USER_INPUT", - "http": { - "status": 400, - }, - "userFriendlyMessage": "An error occurred.", - }, - "message": "Variable "$id" of required type "UUID!" was not provided.", - "name": "GraphQLError", -} -`; - exports[`CommandMenuItem deletion should fail when deleting with empty id 1`] = ` { "extensions": { @@ -36,7 +22,7 @@ exports[`CommandMenuItem deletion should fail when deleting with empty id 1`] = "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; @@ -55,3 +41,17 @@ exports[`CommandMenuItem deletion should fail when deleting with invalid id (not "name": "ValidationError", } `; + +exports[`CommandMenuItem deletion should fail when deleting with missing id 1`] = ` +{ + "extensions": { + "code": "BAD_USER_INPUT", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + }, + "message": "Variable "$id" of required type "UUID!" was not provided.", + "name": "GraphQLError", +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap index 10d9158cfe..5077ce60da 100644 --- a/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/command-menu-item/__snapshots__/failing-command-menu-item-update.integration-spec.ts.snap @@ -22,7 +22,7 @@ exports[`CommandMenuItem update should fail when updating with empty id 1`] = ` "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap index 741ebaf03b..4eb3882a9c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-creation.integration-spec.ts.snap @@ -10,7 +10,7 @@ exports[`NavigationMenuItem creation should fail when creating with empty target "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; @@ -25,7 +25,7 @@ exports[`NavigationMenuItem creation should fail when creating with empty target "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; @@ -141,4 +141,3 @@ exports[`NavigationMenuItem creation should fail when creating with missing targ "name": "NotFoundError", } `; - diff --git a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-deletion.integration-spec.ts.snap index 0a46bba97c..4f1e32717a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-deletion.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-deletion.integration-spec.ts.snap @@ -22,7 +22,7 @@ exports[`NavigationMenuItem deletion should fail when deleting with empty id 1`] "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-update.integration-spec.ts.snap index 6ba2957d47..386acd4edb 100644 --- a/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/navigation-menu-item/__snapshots__/failing-navigation-menu-item-update.integration-spec.ts.snap @@ -22,7 +22,7 @@ exports[`NavigationMenuItem update should fail when updating with empty id 1`] = "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; @@ -67,7 +67,7 @@ exports[`NavigationMenuItem update should fail when updating with missing id 1`] "userFriendlyMessage": "An error occurred.", "value": "", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: ''", "name": "ValidationError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap index 8db0da5565..9991a1bd5f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap @@ -65,7 +65,7 @@ exports[`Object metadata update should fail when labelIdentifier is not a uuid 1 "userFriendlyMessage": "An error occurred.", "value": "not-a-uuid", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: 'not-a-uuid'", "name": "ValidationError", }, ] diff --git a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap index 38f1ff20bb..9c736ff343 100644 --- a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/__snapshots__/failing-row-level-permission-predicate-upsert.integration-spec.ts.snap @@ -22,7 +22,7 @@ exports[`Row Level Permission Predicate upsert should fail when fieldMetadataId "userFriendlyMessage": "An error occurred.", "value": "invalid-uuid", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: 'invalid-uuid'", "name": "ValidationError", } `; @@ -49,7 +49,7 @@ exports[`Row Level Permission Predicate upsert should fail when objectMetadataId "userFriendlyMessage": "An error occurred.", "value": "invalid-uuid", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: 'invalid-uuid'", "name": "ValidationError", } `; @@ -64,7 +64,7 @@ exports[`Row Level Permission Predicate upsert should fail when objectMetadataId "userFriendlyMessage": "An error occurred.", "value": "invalid-uuid", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: 'invalid-uuid'", "name": "ValidationError", } `; @@ -91,7 +91,7 @@ exports[`Row Level Permission Predicate upsert should fail when roleId is not a "userFriendlyMessage": "An error occurred.", "value": "invalid-uuid", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: 'invalid-uuid'", "name": "ValidationError", } `; diff --git a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap index 860fbad3e9..81853f276e 100644 --- a/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/view-filter-group/__snapshots__/failing-view-filter-group-creation.integration-spec.ts.snap @@ -46,7 +46,7 @@ exports[`View Filter Group creation should fail when viewId is not a valid UUID "userFriendlyMessage": "An error occurred.", "value": "invalid-uuid", }, - "message": "Invalid UUID: 'not-a-valid-uuid'", + "message": "Invalid UUID: 'invalid-uuid'", "name": "ValidationError", } `; diff --git a/packages/twenty-shared/src/application/applicationType.ts b/packages/twenty-shared/src/application/applicationType.ts index 64f4ebd374..e5fd0aa808 100644 --- a/packages/twenty-shared/src/application/applicationType.ts +++ b/packages/twenty-shared/src/application/applicationType.ts @@ -14,12 +14,13 @@ export type ApplicationMarketplaceData = { export type ApplicationManifest = SyncableEntityOptions & { defaultRoleUniversalIdentifier: string; - postInstallLogicFunctionUniversalIdentifier?: string; displayName: string; description: string; icon?: string; applicationVariables?: ApplicationVariables; marketplaceData?: ApplicationMarketplaceData; + preInstallLogicFunctionUniversalIdentifier?: string; + postInstallLogicFunctionUniversalIdentifier?: string; packageJsonChecksum: string | null; yarnLockChecksum: string | null; apiClientChecksum: string | null;