@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<void>',
|
||||
);
|
||||
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<void>',
|
||||
);
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
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,
|
||||
});
|
||||
`;
|
||||
|
||||
|
||||
@@ -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<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
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 = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
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`.
|
||||
|
||||
|
||||
@@ -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>`: Name of the function to execute (required if `--postInstall` and `-u` not provided).
|
||||
- `-u, --functionUniversalIdentifier <id>`: Universal ID of the function to execute (required if `--postInstall` and `-n` not provided).
|
||||
- `-p, --payload <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
|
||||
```
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,5 +2,8 @@ import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
|
||||
export type ApplicationConfig = Omit<
|
||||
ApplicationManifest,
|
||||
'packageJsonChecksum' | 'yarnLockChecksum' | 'apiClientChecksum'
|
||||
| 'packageJsonChecksum'
|
||||
| 'yarnLockChecksum'
|
||||
| 'apiClientChecksum'
|
||||
| 'postInstallLogicFunctionUniversalIdentifier'
|
||||
>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
+76
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
+76
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export type InstallLogicFunctionPayload = {
|
||||
previousVersion: string;
|
||||
};
|
||||
|
||||
export type InstallLogicFunctionHandler = (
|
||||
payload: InstallLogicFunctionPayload,
|
||||
) => any | Promise<any>;
|
||||
+2
-2
@@ -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',
|
||||
},
|
||||
|
||||
+1
-1
@@ -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\\""]"`;
|
||||
|
||||
+1
-1
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
+15
-15
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
+1
-1
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
+2
-3
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
+1
-1
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
+2
-2
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
+1
-1
@@ -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",
|
||||
},
|
||||
]
|
||||
|
||||
+4
-4
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
+1
-1
@@ -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",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user