Baptiste Devessier
2026-04-02 13:20:08 +02:00
committed by GitHub
parent 268543a08e
commit 885ab8b444
20 changed files with 1087 additions and 275 deletions
@@ -2281,6 +2281,33 @@ type FieldConnection {
edges: [FieldEdge!]!
}
type AuthToken {
token: String!
expiresAt: DateTime!
}
type ApplicationTokenPair {
applicationAccessToken: AuthToken!
applicationRefreshToken: AuthToken!
}
type FrontComponent {
id: UUID!
name: String!
description: String
sourceComponentPath: String!
builtComponentPath: String!
componentName: String!
builtComponentChecksum: String!
universalIdentifier: UUID
applicationId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isHeadless: Boolean!
usesSdkClient: Boolean!
applicationTokenPair: ApplicationTokenPair
}
type LogicFunctionLogs {
"""Execution Logs"""
logs: String!
@@ -2303,11 +2330,6 @@ type AuthorizeApp {
redirectUrl: String!
}
type AuthToken {
token: String!
expiresAt: DateTime!
}
type AuthTokenPair {
accessOrWorkspaceAgnosticToken: AuthToken!
refreshToken: AuthToken!
@@ -2416,11 +2438,6 @@ type WorkspaceMigration {
actions: JSON!
}
type ApplicationTokenPair {
applicationAccessToken: AuthToken!
applicationRefreshToken: AuthToken!
}
type File {
id: UUID!
path: String!
@@ -2540,23 +2557,6 @@ type PostgresCredentials {
workspaceId: UUID!
}
type FrontComponent {
id: UUID!
name: String!
description: String
sourceComponentPath: String!
builtComponentPath: String!
componentName: String!
builtComponentChecksum: String!
universalIdentifier: UUID
applicationId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isHeadless: Boolean!
usesSdkClient: Boolean!
applicationTokenPair: ApplicationTokenPair
}
type CommandMenuItem {
id: UUID!
workflowVersionId: UUID
@@ -1955,6 +1955,36 @@ export interface FieldConnection {
__typename: 'FieldConnection'
}
export interface AuthToken {
token: Scalars['String']
expiresAt: Scalars['DateTime']
__typename: 'AuthToken'
}
export interface ApplicationTokenPair {
applicationAccessToken: AuthToken
applicationRefreshToken: AuthToken
__typename: 'ApplicationTokenPair'
}
export interface FrontComponent {
id: Scalars['UUID']
name: Scalars['String']
description?: Scalars['String']
sourceComponentPath: Scalars['String']
builtComponentPath: Scalars['String']
componentName: Scalars['String']
builtComponentChecksum: Scalars['String']
universalIdentifier?: Scalars['UUID']
applicationId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isHeadless: Scalars['Boolean']
usesSdkClient: Scalars['Boolean']
applicationTokenPair?: ApplicationTokenPair
__typename: 'FrontComponent'
}
export interface LogicFunctionLogs {
/** Execution Logs */
logs: Scalars['String']
@@ -1982,12 +2012,6 @@ export interface AuthorizeApp {
__typename: 'AuthorizeApp'
}
export interface AuthToken {
token: Scalars['String']
expiresAt: Scalars['DateTime']
__typename: 'AuthToken'
}
export interface AuthTokenPair {
accessOrWorkspaceAgnosticToken: AuthToken
refreshToken: AuthToken
@@ -2117,12 +2141,6 @@ export interface WorkspaceMigration {
__typename: 'WorkspaceMigration'
}
export interface ApplicationTokenPair {
applicationAccessToken: AuthToken
applicationRefreshToken: AuthToken
__typename: 'ApplicationTokenPair'
}
export interface File {
id: Scalars['UUID']
path: Scalars['String']
@@ -2249,24 +2267,6 @@ export interface PostgresCredentials {
__typename: 'PostgresCredentials'
}
export interface FrontComponent {
id: Scalars['UUID']
name: Scalars['String']
description?: Scalars['String']
sourceComponentPath: Scalars['String']
builtComponentPath: Scalars['String']
componentName: Scalars['String']
builtComponentChecksum: Scalars['String']
universalIdentifier?: Scalars['UUID']
applicationId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
isHeadless: Scalars['Boolean']
usesSdkClient: Scalars['Boolean']
applicationTokenPair?: ApplicationTokenPair
__typename: 'FrontComponent'
}
export interface CommandMenuItem {
id: Scalars['UUID']
workflowVersionId?: Scalars['UUID']
@@ -5097,6 +5097,39 @@ export interface FieldConnectionGenqlSelection{
__scalar?: boolean | number
}
export interface AuthTokenGenqlSelection{
token?: boolean | number
expiresAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface ApplicationTokenPairGenqlSelection{
applicationAccessToken?: AuthTokenGenqlSelection
applicationRefreshToken?: AuthTokenGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FrontComponentGenqlSelection{
id?: boolean | number
name?: boolean | number
description?: boolean | number
sourceComponentPath?: boolean | number
builtComponentPath?: boolean | number
componentName?: boolean | number
builtComponentChecksum?: boolean | number
universalIdentifier?: boolean | number
applicationId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isHeadless?: boolean | number
usesSdkClient?: boolean | number
applicationTokenPair?: ApplicationTokenPairGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface LogicFunctionLogsGenqlSelection{
/** Execution Logs */
logs?: boolean | number
@@ -5129,13 +5162,6 @@ export interface AuthorizeAppGenqlSelection{
__scalar?: boolean | number
}
export interface AuthTokenGenqlSelection{
token?: boolean | number
expiresAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AuthTokenPairGenqlSelection{
accessOrWorkspaceAgnosticToken?: AuthTokenGenqlSelection
refreshToken?: AuthTokenGenqlSelection
@@ -5286,13 +5312,6 @@ export interface WorkspaceMigrationGenqlSelection{
__scalar?: boolean | number
}
export interface ApplicationTokenPairGenqlSelection{
applicationAccessToken?: AuthTokenGenqlSelection
applicationRefreshToken?: AuthTokenGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FileGenqlSelection{
id?: boolean | number
path?: boolean | number
@@ -5429,25 +5448,6 @@ export interface PostgresCredentialsGenqlSelection{
__scalar?: boolean | number
}
export interface FrontComponentGenqlSelection{
id?: boolean | number
name?: boolean | number
description?: boolean | number
sourceComponentPath?: boolean | number
builtComponentPath?: boolean | number
componentName?: boolean | number
builtComponentChecksum?: boolean | number
universalIdentifier?: boolean | number
applicationId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
isHeadless?: boolean | number
usesSdkClient?: boolean | number
applicationTokenPair?: ApplicationTokenPairGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface CommandMenuItemGenqlSelection{
id?: boolean | number
workflowVersionId?: boolean | number
@@ -8030,6 +8030,30 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const AuthToken_possibleTypes: string[] = ['AuthToken']
export const isAuthToken = (obj?: { __typename?: any } | null): obj is AuthToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAuthToken"')
return AuthToken_possibleTypes.includes(obj.__typename)
}
const ApplicationTokenPair_possibleTypes: string[] = ['ApplicationTokenPair']
export const isApplicationTokenPair = (obj?: { __typename?: any } | null): obj is ApplicationTokenPair => {
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationTokenPair"')
return ApplicationTokenPair_possibleTypes.includes(obj.__typename)
}
const FrontComponent_possibleTypes: string[] = ['FrontComponent']
export const isFrontComponent = (obj?: { __typename?: any } | null): obj is FrontComponent => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFrontComponent"')
return FrontComponent_possibleTypes.includes(obj.__typename)
}
const LogicFunctionLogs_possibleTypes: string[] = ['LogicFunctionLogs']
export const isLogicFunctionLogs = (obj?: { __typename?: any } | null): obj is LogicFunctionLogs => {
if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionLogs"')
@@ -8070,14 +8094,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const AuthToken_possibleTypes: string[] = ['AuthToken']
export const isAuthToken = (obj?: { __typename?: any } | null): obj is AuthToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAuthToken"')
return AuthToken_possibleTypes.includes(obj.__typename)
}
const AuthTokenPair_possibleTypes: string[] = ['AuthTokenPair']
export const isAuthTokenPair = (obj?: { __typename?: any } | null): obj is AuthTokenPair => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAuthTokenPair"')
@@ -8246,14 +8262,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const ApplicationTokenPair_possibleTypes: string[] = ['ApplicationTokenPair']
export const isApplicationTokenPair = (obj?: { __typename?: any } | null): obj is ApplicationTokenPair => {
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationTokenPair"')
return ApplicationTokenPair_possibleTypes.includes(obj.__typename)
}
const File_possibleTypes: string[] = ['File']
export const isFile = (obj?: { __typename?: any } | null): obj is File => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFile"')
@@ -8366,14 +8374,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const FrontComponent_possibleTypes: string[] = ['FrontComponent']
export const isFrontComponent = (obj?: { __typename?: any } | null): obj is FrontComponent => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFrontComponent"')
return FrontComponent_possibleTypes.includes(obj.__typename)
}
const CommandMenuItem_possibleTypes: string[] = ['CommandMenuItem']
export const isCommandMenuItem = (obj?: { __typename?: any } | null): obj is CommandMenuItem => {
if (!obj?.__typename) throw new Error('__typename is missing in "isCommandMenuItem"')
@@ -63,8 +63,8 @@ export default {
213,
230,
247,
283,
284,
285,
295,
296,
324,
@@ -4495,6 +4495,75 @@ export default {
1
]
},
"AuthToken": {
"token": [
1
],
"expiresAt": [
4
],
"__typename": [
1
]
},
"ApplicationTokenPair": {
"applicationAccessToken": [
249
],
"applicationRefreshToken": [
249
],
"__typename": [
1
]
},
"FrontComponent": {
"id": [
3
],
"name": [
1
],
"description": [
1
],
"sourceComponentPath": [
1
],
"builtComponentPath": [
1
],
"componentName": [
1
],
"builtComponentChecksum": [
1
],
"universalIdentifier": [
3
],
"applicationId": [
3
],
"createdAt": [
4
],
"updatedAt": [
4
],
"isHeadless": [
6
],
"usesSdkClient": [
6
],
"applicationTokenPair": [
250
],
"__typename": [
1
]
},
"LogicFunctionLogs": {
"logs": [
1
@@ -4535,23 +4604,12 @@ export default {
1
]
},
"AuthToken": {
"token": [
1
],
"expiresAt": [
4
],
"__typename": [
1
]
},
"AuthTokenPair": {
"accessOrWorkspaceAgnosticToken": [
254
249
],
"refreshToken": [
254
249
],
"__typename": [
1
@@ -4559,7 +4617,7 @@ export default {
},
"AvailableWorkspacesAndAccessTokens": {
"tokens": [
255
257
],
"availableWorkspaces": [
227
@@ -4611,10 +4669,10 @@ export default {
},
"SignUp": {
"loginToken": [
254
249
],
"workspace": [
260
262
],
"__typename": [
1
@@ -4622,7 +4680,7 @@ export default {
},
"TransientToken": {
"transientToken": [
254
249
],
"__typename": [
1
@@ -4644,7 +4702,7 @@ export default {
},
"VerifyEmailAndGetLoginToken": {
"loginToken": [
254
249
],
"workspaceUrls": [
157
@@ -4663,7 +4721,7 @@ export default {
},
"AuthTokens": {
"tokens": [
255
257
],
"__typename": [
1
@@ -4671,7 +4729,7 @@ export default {
},
"LoginToken": {
"loginToken": [
254
249
],
"__typename": [
1
@@ -4701,10 +4759,10 @@ export default {
},
"Impersonate": {
"loginToken": [
254
249
],
"workspace": [
260
262
],
"__typename": [
1
@@ -4726,7 +4784,7 @@ export default {
1
],
"dailyUsage": [
271
273
],
"__typename": [
1
@@ -4743,7 +4801,7 @@ export default {
181
],
"timeSeries": [
271
273
],
"periodStart": [
4
@@ -4752,7 +4810,7 @@ export default {
4
],
"userDailyUsage": [
272
274
],
"__typename": [
1
@@ -4780,17 +4838,6 @@ export default {
1
]
},
"ApplicationTokenPair": {
"applicationAccessToken": [
254
],
"applicationRefreshToken": [
254
],
"__typename": [
1
]
},
"File": {
"id": [
3
@@ -4920,13 +4967,13 @@ export default {
1
],
"driver": [
283
],
"status": [
284
],
"status": [
285
],
"verificationRecords": [
281
282
],
"verifiedAt": [
4
@@ -4973,7 +5020,7 @@ export default {
1
],
"location": [
286
287
],
"__typename": [
1
@@ -5001,13 +5048,13 @@ export default {
},
"ImapSmtpCaldavConnectionParameters": {
"IMAP": [
288
289
],
"SMTP": [
288
289
],
"CALDAV": [
288
289
],
"__typename": [
1
@@ -5027,7 +5074,7 @@ export default {
3
],
"connectionParameters": [
289
290
],
"__typename": [
1
@@ -5061,53 +5108,6 @@ export default {
1
]
},
"FrontComponent": {
"id": [
3
],
"name": [
1
],
"description": [
1
],
"sourceComponentPath": [
1
],
"builtComponentPath": [
1
],
"componentName": [
1
],
"builtComponentChecksum": [
1
],
"universalIdentifier": [
3
],
"applicationId": [
3
],
"createdAt": [
4
],
"updatedAt": [
4
],
"isHeadless": [
6
],
"usesSdkClient": [
6
],
"applicationTokenPair": [
276
],
"__typename": [
1
]
},
"CommandMenuItem": {
"id": [
3
@@ -5119,7 +5119,7 @@ export default {
3
],
"frontComponent": [
293
251
],
"engineComponentKey": [
295
@@ -6282,10 +6282,10 @@ export default {
}
],
"frontComponents": [
293
251
],
"frontComponent": [
293,
251,
{
"id": [
3,
@@ -6574,7 +6574,7 @@ export default {
}
],
"checkUserExists": [
268,
270,
{
"email": [
1,
@@ -6586,7 +6586,7 @@ export default {
}
],
"checkWorkspaceInviteHashIsValid": [
269,
271,
{
"inviteHash": [
1,
@@ -6604,7 +6604,7 @@ export default {
}
],
"validatePasswordResetToken": [
263,
265,
{
"passwordResetToken": [
1,
@@ -6696,7 +6696,7 @@ export default {
223
],
"getConnectedImapSmtpCaldavAccount": [
290,
291,
{
"id": [
3,
@@ -6705,7 +6705,7 @@ export default {
}
],
"getAutoCompleteAddress": [
285,
286,
{
"address": [
1,
@@ -6724,7 +6724,7 @@ export default {
}
],
"getAddressDetails": [
287,
288,
{
"placeId": [
1,
@@ -6830,7 +6830,7 @@ export default {
206
],
"getUsageAnalytics": [
273,
275,
{
"input": [
364
@@ -6838,19 +6838,19 @@ export default {
}
],
"getPostgresCredentials": [
292
293
],
"findManyPublicDomains": [
280
281
],
"getEmailingDomains": [
282
283
],
"findManyMarketplaceApps": [
278
279
],
"findMarketplaceAppDetail": [
279,
280,
{
"universalIdentifier": [
1,
@@ -7798,7 +7798,7 @@ export default {
}
],
"createFrontComponent": [
293,
251,
{
"input": [
418,
@@ -7807,7 +7807,7 @@ export default {
}
],
"updateFrontComponent": [
293,
251,
{
"input": [
419,
@@ -7816,7 +7816,7 @@ export default {
}
],
"deleteFrontComponent": [
293,
251,
{
"id": [
3,
@@ -8295,7 +8295,7 @@ export default {
}
],
"getAuthorizationUrlForSSO": [
258,
260,
{
"input": [
466,
@@ -8304,7 +8304,7 @@ export default {
}
],
"getLoginTokenFromCredentials": [
267,
269,
{
"email": [
1,
@@ -8330,7 +8330,7 @@ export default {
}
],
"signIn": [
256,
258,
{
"email": [
1,
@@ -8352,7 +8352,7 @@ export default {
}
],
"verifyEmailAndGetLoginToken": [
264,
266,
{
"emailVerificationToken": [
1,
@@ -8372,7 +8372,7 @@ export default {
}
],
"verifyEmailAndGetWorkspaceAgnosticToken": [
256,
258,
{
"emailVerificationToken": [
1,
@@ -8388,7 +8388,7 @@ export default {
}
],
"getAuthTokensFromOTP": [
266,
268,
{
"otp": [
1,
@@ -8408,7 +8408,7 @@ export default {
}
],
"signUp": [
256,
258,
{
"email": [
1,
@@ -8430,7 +8430,7 @@ export default {
}
],
"signUpInWorkspace": [
261,
263,
{
"email": [
1,
@@ -8461,13 +8461,13 @@ export default {
}
],
"signUpInNewWorkspace": [
261
263
],
"generateTransientToken": [
262
264
],
"getAuthTokensFromLoginToken": [
266,
268,
{
"loginToken": [
1,
@@ -8480,7 +8480,7 @@ export default {
}
],
"authorizeApp": [
253,
256,
{
"clientId": [
1,
@@ -8502,7 +8502,7 @@ export default {
}
],
"renewToken": [
266,
268,
{
"appToken": [
1,
@@ -8511,7 +8511,7 @@ export default {
}
],
"generateApiKeyToken": [
265,
267,
{
"apiKeyId": [
3,
@@ -8524,7 +8524,7 @@ export default {
}
],
"emailPasswordResetLink": [
257,
259,
{
"email": [
1,
@@ -8536,7 +8536,7 @@ export default {
}
],
"updatePasswordViaResetToken": [
259,
261,
{
"passwordResetToken": [
1,
@@ -8637,7 +8637,7 @@ export default {
}
],
"initiateOTPProvisioning": [
251,
254,
{
"loginToken": [
1,
@@ -8650,10 +8650,10 @@ export default {
}
],
"initiateOTPProvisioningForAuthenticatedUser": [
251
254
],
"deleteTwoFactorAuthenticationMethod": [
250,
253,
{
"twoFactorAuthenticationMethodId": [
3,
@@ -8662,7 +8662,7 @@ export default {
}
],
"verifyTwoFactorAuthenticationMethodForAuthenticatedUser": [
252,
255,
{
"otp": [
1,
@@ -8768,7 +8768,7 @@ export default {
}
],
"impersonate": [
270,
272,
{
"userId": [
3,
@@ -8790,7 +8790,7 @@ export default {
}
],
"saveImapSmtpCaldavAccount": [
291,
292,
{
"accountOwnerId": [
3,
@@ -9012,13 +9012,13 @@ export default {
6
],
"enablePostgresProxy": [
292
293
],
"disablePostgresProxy": [
292
293
],
"createPublicDomain": [
280,
281,
{
"domain": [
1,
@@ -9045,14 +9045,14 @@ export default {
}
],
"createEmailingDomain": [
282,
283,
{
"domain": [
1,
"String!"
],
"driver": [
283,
284,
"EmailingDomainDriver!"
]
}
@@ -9067,7 +9067,7 @@ export default {
}
],
"verifyEmailingDomain": [
282,
283,
{
"id": [
1,
@@ -9147,7 +9147,7 @@ export default {
}
],
"createDevelopmentApplication": [
274,
276,
{
"universalIdentifier": [
1,
@@ -9160,7 +9160,7 @@ export default {
}
],
"generateApplicationToken": [
276,
250,
{
"applicationId": [
3,
@@ -9169,7 +9169,7 @@ export default {
}
],
"syncApplication": [
275,
277,
{
"manifest": [
15,
@@ -9178,7 +9178,7 @@ export default {
}
],
"uploadApplicationFile": [
277,
278,
{
"file": [
372,
@@ -9212,7 +9212,7 @@ export default {
}
],
"renewApplicationToken": [
276,
250,
{
"applicationRefreshToken": [
1,
@@ -11448,7 +11448,7 @@ export default {
}
],
"logicFunctionLogs": [
249,
252,
{
"input": [
490,
+2 -1
View File
@@ -13,7 +13,8 @@
"src/engine/workspace-manager/dev-seeder/data/seeds/**",
"src/utils/email-providers.ts",
"src/engine/core-modules/i18n/locales/generated/**",
"src/engine/core-modules/logic-function/logic-function-resource/constants/seed-project/src/index.ts"
"src/engine/core-modules/logic-function/logic-function-resource/constants/seed-project/src/index.ts",
"src/engine/metadata-modules/front-component/constants/seed-project/**"
],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
+2 -1
View File
@@ -1 +1,2 @@
**/generated/*
**/generated/*
src/engine/metadata-modules/front-component/constants/seed-project/**
+4
View File
@@ -20,6 +20,10 @@
"include": "engine/core-modules/logic-function/logic-function-resource/constants/seed-project/**",
"outDir": "dist/assets"
},
{
"include": "engine/metadata-modules/front-component/constants/seed-project/**",
"outDir": "dist/assets"
},
{
"include": "engine/core-modules/application/application-package/constants/seed-dependencies/**",
"outDir": "dist/assets"
@@ -0,0 +1,58 @@
// Script to build seed front component .tsx files into .mjs using the same
// esbuild pipeline as the twenty-sdk CLI (Remote DOM + JSX wrapping).
//
// Usage: npx tsx scripts/build-seed-front-components.ts
import * as esbuild from 'esbuild';
import { join, resolve } from 'path';
import { getFrontComponentBuildPlugins } from 'twenty-sdk/front-component-renderer/build';
const ROOT_DIR = resolve(__dirname, '../../..');
const ROOT_NODE_MODULES = resolve(ROOT_DIR, 'node_modules');
const SEED_PROJECT_DIR = resolve(
__dirname,
'../src/engine/metadata-modules/front-component/constants/seed-project',
);
const alias: Record<string, string> = {
react: join(ROOT_NODE_MODULES, 'react'),
'react-dom': join(ROOT_NODE_MODULES, 'react-dom'),
};
const COMPONENTS = ['hello-world', 'show-notification'];
const build = async () => {
for (const component of COMPONENTS) {
const entryPoint = join(SEED_PROJECT_DIR, component, 'index.tsx');
const outdir = join(SEED_PROJECT_DIR, component);
await esbuild.build({
entryPoints: [entryPoint],
bundle: true,
splitting: false,
format: 'esm',
outExtension: { '.js': '.mjs' },
external: [
'twenty-client-sdk/core',
'twenty-client-sdk/metadata',
'twenty-shared/*',
],
jsx: 'automatic',
sourcemap: false,
metafile: false,
minify: true,
logLevel: 'info',
outdir,
alias,
plugins: [...getFrontComponentBuildPlugins()],
});
console.log(`Built ${component}/index.mjs`);
}
};
build().catch((error) => {
console.error('Build failed:', error);
process.exit(1);
});
@@ -33,7 +33,7 @@ export const fromCreateFrontComponentInputToFlatFrontComponentToCreate = ({
builtComponentPath: createFrontComponentInput.builtComponentPath,
componentName: createFrontComponentInput.componentName,
builtComponentChecksum: createFrontComponentInput.builtComponentChecksum,
isHeadless: false,
isHeadless: createFrontComponentInput.isHeadless ?? false,
usesSdkClient: false,
workspaceId,
createdAt: now,
@@ -0,0 +1,120 @@
import {
defineFrontComponent,
useFrontComponentExecutionContext,
useUserId,
} from 'twenty-sdk';
import { useState } from 'react';
const HelloWorld = () => {
const [renderCount, setRenderCount] = useState(0);
const userId = useUserId();
const fullContext = useFrontComponentExecutionContext((context) => context);
return (
<div
style={{
padding: 24,
display: 'flex',
flexDirection: 'column',
gap: 16,
fontFamily: 'system-ui, sans-serif',
background: '#f0f9ff',
borderRadius: 12,
border: '2px solid #38bdf8',
maxWidth: 400,
}}
>
<h2
style={{
color: '#0369a1',
fontWeight: 700,
fontSize: 18,
margin: 0,
}}
>
Hello World
</h2>
<div>
<p
style={{
fontSize: 12,
fontWeight: 600,
color: '#64748b',
textTransform: 'uppercase',
letterSpacing: 1,
}}
>
User ID
</p>
<p
style={{
fontSize: 16,
fontWeight: 700,
color: '#0c4a6e',
wordBreak: 'break-all',
}}
>
{userId ?? 'null'}
</p>
</div>
<div>
<p
style={{
fontSize: 12,
fontWeight: 600,
color: '#64748b',
textTransform: 'uppercase',
letterSpacing: 1,
}}
>
Execution Context
</p>
<pre
style={{
fontSize: 13,
fontWeight: 700,
color: '#0c4a6e',
background: '#e0f2fe',
padding: 12,
borderRadius: 8,
overflow: 'auto',
wordBreak: 'break-all',
}}
>
{JSON.stringify(fullContext, null, 2) ?? 'undefined'}
</pre>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<button
onClick={() => setRenderCount((c) => c + 1)}
style={{
padding: '8px 16px',
backgroundColor: '#0284c7',
color: 'white',
border: 'none',
borderRadius: 6,
fontWeight: 600,
cursor: 'pointer',
}}
>
Re-render
</button>
<span style={{ fontSize: 14, color: '#64748b' }}>
Render count: {renderCount}
</span>
</div>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'seed-front-component-hello-world',
name: 'Hello World',
description:
'A sample visual front component that displays execution context',
component: HelloWorld,
});
@@ -0,0 +1,32 @@
import {
defineFrontComponent,
enqueueSnackbar,
unmountFrontComponent,
} from 'twenty-sdk';
import { useEffect } from 'react';
const ShowNotification = () => {
useEffect(() => {
const run = async () => {
await enqueueSnackbar({
message: 'Hello from a headless front component!',
variant: 'success',
duration: 5000,
});
await unmountFrontComponent();
};
run();
}, []);
return null;
};
export default defineFrontComponent({
universalIdentifier: 'seed-front-component-show-notification',
name: 'Show Notification',
description: 'A sample headless front component that displays a notification',
isHeadless: true,
component: ShowNotification,
});
@@ -1,6 +1,12 @@
import { Field, HideField, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@@ -16,6 +22,11 @@ export class CreateFrontComponentInput {
@HideField()
universalIdentifier?: string;
@IsBoolean()
@IsOptional()
@HideField()
isHeadless?: boolean;
@IsString()
@IsOptional()
@Field()
@@ -0,0 +1,46 @@
import fs from 'fs/promises';
import path from 'path';
import { ASSET_PATH } from 'src/constants/assets-path';
export type FrontComponentSeedProjectFile = {
name: string;
path: string;
content: Buffer;
};
const getAllFiles = async (
rootDir: string,
dir: string = rootDir,
files: FrontComponentSeedProjectFile[] = [],
): Promise<FrontComponentSeedProjectFile[]> => {
const dirEntries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of dirEntries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await getAllFiles(rootDir, fullPath, files);
} else {
files.push({
path: path.relative(rootDir, dir),
name: entry.name,
content: await fs.readFile(fullPath),
});
}
}
return files;
};
export const getFrontComponentSeedProjectFiles = async (
subdirectory: string,
): Promise<FrontComponentSeedProjectFile[]> => {
const seedProjectPath = path.join(
ASSET_PATH,
'engine/metadata-modules/front-component/constants/seed-project',
subdirectory,
);
return getAllFiles(seedProjectPath);
};
@@ -5,7 +5,7 @@ import { readFile } from 'fs/promises';
import { join } from 'path';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { FileFolder, FeatureFlagKey } from 'twenty-shared/types';
import { FeatureFlagKey, FileFolder } from 'twenty-shared/types';
import { DataSource } from 'typeorm';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
@@ -56,10 +56,6 @@ import {
MESSAGE_CHANNEL_DATA_SEED_COLUMNS,
MESSAGE_CHANNEL_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant';
import {
MESSAGE_FOLDER_DATA_SEED_COLUMNS,
MESSAGE_FOLDER_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-folder-data-seeds.constant';
import {
MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_COLUMNS,
MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEEDS,
@@ -68,6 +64,10 @@ import {
MESSAGE_DATA_SEED_COLUMNS,
MESSAGE_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-data-seeds.constant';
import {
MESSAGE_FOLDER_DATA_SEED_COLUMNS,
MESSAGE_FOLDER_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-folder-data-seeds.constant';
import {
getMessageParticipantDataSeeds,
MESSAGE_PARTICIPANT_DATA_SEED_COLUMNS,
@@ -121,10 +121,13 @@ import {
WORKSPACE_MEMBER_DATA_SEED_COLUMNS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
import { TimelineActivitySeederService } from 'src/engine/workspace-manager/dev-seeder/data/services/timeline-activity-seeder.service';
import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util';
import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util';
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util';
import { PrefillFrontComponentService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-front-component.service';
import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service';
import { prefillFrontComponentCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-command-menu-items.util';
import { getSeedFrontComponentDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util';
import { getCreateCompanyWhenAddingNewPersonCodeStepLogicFunctionDefinitions } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-code-step-logic-functions.util';
import { prefillWorkflowCommandMenuItems } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflow-command-menu-items.util';
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-workflows.util';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
type RecordSeedConfig = {
@@ -307,6 +310,7 @@ export class DevSeederDataService {
private readonly fileStorageService: FileStorageService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly prefillLogicFunctionService: PrefillLogicFunctionService,
private readonly prefillFrontComponentService: PrefillFrontComponentService,
) {}
public async seed({
@@ -342,6 +346,11 @@ export class DevSeederDataService {
),
});
await this.prefillFrontComponentService.ensureSeeded({
workspaceId,
definitions: getSeedFrontComponentDefinitions(workspaceId),
});
await this.coreDataSource.transaction(
async (entityManager: WorkspaceEntityManager) => {
await this.seedRecordsInBatches({
@@ -377,6 +386,8 @@ export class DevSeederDataService {
);
await prefillWorkflowCommandMenuItems(entityManager, workspaceId);
await prefillFrontComponentCommandMenuItems(entityManager, workspaceId);
},
);
}
@@ -0,0 +1,129 @@
import crypto from 'crypto';
import { Injectable } from '@nestjs/common';
import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { FrontComponentService } from 'src/engine/metadata-modules/front-component/front-component.service';
import {
type FrontComponentSeedProjectFile,
getFrontComponentSeedProjectFiles,
} from 'src/engine/metadata-modules/front-component/utils/get-front-component-seed-project-files.util';
import { type SeedFrontComponentDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util';
@Injectable()
export class PrefillFrontComponentService {
constructor(
private readonly frontComponentService: FrontComponentService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly fileStorageService: FileStorageService,
private readonly applicationService: ApplicationService,
) {}
async ensureSeeded({
workspaceId,
definitions,
}: {
workspaceId: string;
definitions: SeedFrontComponentDefinition[];
}) {
const { flatFrontComponentMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatFrontComponentMaps'],
},
);
const { workspaceCustomFlatApplication: ownerFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
for (const definition of definitions) {
const existingFrontComponent = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: definition.id,
flatEntityMaps: flatFrontComponentMaps,
});
if (isDefined(existingFrontComponent)) {
continue;
}
const seedFiles = await getFrontComponentSeedProjectFiles(
definition.seedProjectSubdir,
);
const sourceFile = seedFiles.find((file: FrontComponentSeedProjectFile) =>
file.name.endsWith('index.tsx'),
);
const builtFile = seedFiles.find((file: FrontComponentSeedProjectFile) =>
file.name.endsWith('index.mjs'),
);
if (!isDefined(sourceFile) || !isDefined(builtFile)) {
throw new Error(
`Seed project for front component "${definition.name}" must have an index.tsx and an index.mjs file`,
);
}
const sourceComponentPath = `${definition.id}/index.tsx`;
const builtComponentPath = `${definition.id}/index.mjs`;
await this.fileStorageService.writeFile({
workspaceId,
applicationUniversalIdentifier:
ownerFlatApplication.universalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: sourceComponentPath,
sourceFile: sourceFile.content,
mimeType: 'application/typescript',
settings: {
isTemporaryFile: false,
toDelete: false,
},
});
await this.fileStorageService.writeFile({
workspaceId,
applicationUniversalIdentifier:
ownerFlatApplication.universalIdentifier,
fileFolder: FileFolder.BuiltFrontComponent,
resourcePath: builtComponentPath,
sourceFile: builtFile.content,
mimeType: 'application/javascript',
settings: {
isTemporaryFile: false,
toDelete: false,
},
});
const checksum = crypto
.createHash('md5')
.update(builtFile.content)
.digest('hex');
await this.frontComponentService.createOne({
workspaceId,
ownerFlatApplication,
input: {
id: definition.id,
universalIdentifier: definition.universalIdentifier,
name: definition.name,
description: definition.description,
componentName: definition.componentName,
sourceComponentPath,
builtComponentPath,
builtComponentChecksum: checksum,
isHeadless: definition.isHeadless,
},
});
}
}
}
@@ -1,12 +1,22 @@
import { Module } from '@nestjs/common';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FrontComponentModule } from 'src/engine/metadata-modules/front-component/front-component.module';
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
import { PrefillFrontComponentService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-front-component.service';
import { PrefillLogicFunctionService } from 'src/engine/workspace-manager/standard-objects-prefill-data/services/prefill-logic-function.service';
@Module({
imports: [LogicFunctionModule, WorkspaceManyOrAllFlatEntityMapsCacheModule],
providers: [PrefillLogicFunctionService],
exports: [PrefillLogicFunctionService],
imports: [
LogicFunctionModule,
FrontComponentModule,
FileStorageModule,
ApplicationModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
],
providers: [PrefillLogicFunctionService, PrefillFrontComponentService],
exports: [PrefillLogicFunctionService, PrefillFrontComponentService],
})
export class StandardObjectsPrefillModule {}
@@ -0,0 +1,72 @@
import { type EntityManager } from 'typeorm';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
import {
getSeedFrontComponentCommandMenuItemDefinitions,
getSeedFrontComponentIds,
} from 'src/engine/workspace-manager/standard-objects-prefill-data/utils/prefill-front-component-definitions.util';
export const prefillFrontComponentCommandMenuItems = async (
entityManager: EntityManager,
workspaceId: string,
) => {
const { helloWorldId } = getSeedFrontComponentIds(workspaceId);
const frontComponentRow = await entityManager
.createQueryBuilder()
.select('fc.applicationId', 'applicationId')
.from('core.frontComponent', 'fc')
.where('fc.id = :id', { id: helloWorldId })
.andWhere('fc.workspaceId = :workspaceId', { workspaceId })
.getRawOne();
if (!frontComponentRow) {
return;
}
const definitions =
getSeedFrontComponentCommandMenuItemDefinitions(workspaceId);
await entityManager
.createQueryBuilder()
.insert()
.into('core.commandMenuItem', [
'workspaceId',
'universalIdentifier',
'applicationId',
'workflowVersionId',
'frontComponentId',
'engineComponentKey',
'label',
'icon',
'shortLabel',
'position',
'isPinned',
'availabilityType',
'conditionalAvailabilityExpression',
'availabilityObjectMetadataId',
'hotKeys',
])
.values(
definitions.map((definition) => ({
workspaceId,
universalIdentifier: definition.universalIdentifier,
applicationId: frontComponentRow.applicationId,
workflowVersionId: null,
frontComponentId: definition.frontComponentId,
engineComponentKey: EngineComponentKey.FRONT_COMPONENT_RENDERER,
label: definition.label,
icon: definition.icon,
shortLabel: definition.label,
position: definition.position,
isPinned: false,
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
conditionalAvailabilityExpression: null,
availabilityObjectMetadataId: null,
hotKeys: null,
})),
)
.orIgnore()
.execute();
};
@@ -0,0 +1,102 @@
import { v5 as uuidv5 } from 'uuid';
const SEED_FRONT_COMPONENT_ID_NAMESPACE =
'e7a3b1c4-f5d6-4e8a-9b2c-3d4e5f6a7b8c';
export type SeedFrontComponentDefinition = {
id: string;
universalIdentifier: string;
name: string;
description: string;
componentName: string;
isHeadless: boolean;
usesSdkClient: boolean;
seedProjectSubdir: string;
};
export type SeedFrontComponentCommandMenuItemDefinition = {
universalIdentifier: string;
frontComponentId: string;
label: string;
icon: string;
position: number;
};
export const getSeedFrontComponentIds = (workspaceId: string) => ({
helloWorldId: uuidv5(
`${workspaceId}:seed-front-component:hello-world`,
SEED_FRONT_COMPONENT_ID_NAMESPACE,
),
showNotificationId: uuidv5(
`${workspaceId}:seed-front-component:show-notification`,
SEED_FRONT_COMPONENT_ID_NAMESPACE,
),
});
export const getSeedFrontComponentDefinitions = (
workspaceId: string,
): SeedFrontComponentDefinition[] => {
const { helloWorldId, showNotificationId } =
getSeedFrontComponentIds(workspaceId);
return [
{
id: helloWorldId,
universalIdentifier: uuidv5(
`${workspaceId}:seed-front-component-uid:hello-world`,
SEED_FRONT_COMPONENT_ID_NAMESPACE,
),
name: 'Hello World',
description:
'A sample visual front component that displays execution context',
componentName: 'HelloWorld',
isHeadless: false,
usesSdkClient: false,
seedProjectSubdir: 'hello-world',
},
{
id: showNotificationId,
universalIdentifier: uuidv5(
`${workspaceId}:seed-front-component-uid:show-notification`,
SEED_FRONT_COMPONENT_ID_NAMESPACE,
),
name: 'Show Notification',
description:
'A sample headless front component that displays a notification',
componentName: 'ShowNotification',
isHeadless: true,
usesSdkClient: false,
seedProjectSubdir: 'show-notification',
},
];
};
export const getSeedFrontComponentCommandMenuItemDefinitions = (
workspaceId: string,
): SeedFrontComponentCommandMenuItemDefinition[] => {
const { helloWorldId, showNotificationId } =
getSeedFrontComponentIds(workspaceId);
return [
{
universalIdentifier: uuidv5(
`${workspaceId}:seed-front-component-command:hello-world`,
SEED_FRONT_COMPONENT_ID_NAMESPACE,
),
frontComponentId: helloWorldId,
label: 'Hello World',
icon: 'IconAppWindow',
position: 200,
},
{
universalIdentifier: uuidv5(
`${workspaceId}:seed-front-component-command:show-notification`,
SEED_FRONT_COMPONENT_ID_NAMESPACE,
),
frontComponentId: showNotificationId,
label: 'Show Notification',
icon: 'IconBell',
position: 201,
},
];
};
+2 -1
View File
@@ -35,6 +35,7 @@
},
"exclude": [
"node_modules",
"dist"
"dist",
"src/engine/metadata-modules/front-component/constants/seed-project"
]
}