From 3e699c4458ed30d4b66356ee6b6f43c473762817 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:20:33 +0200 Subject: [PATCH] Fix upgrade commands discovery outside of cli (#19671) # Introduction We were allowing the sequence to be empty in the worker context that was facing an edge case importing the UpgradeModule through the WorkspaceModule god module, no commands were discovered and it was throwing as the sequence must have at least one workspace commands to allow a workspace creation Though the issue was also applicable to the twenty-server `AppModule` too that was not discovering any commands ## Integration tests were passing The integration test were importing the `CommandModule` at the nest testing app creating leading to asymmetric testing context It was a requirement for a legacy commands import and global assignation ## Fix The `UpgradeModule` now import both `WorkspaceCommandsProviderModule` and `InstanceCommandProviderModule` which ships the commands directly in the module We could consider moving the commands into the `engine/upgrade` folder ## Concern Bootstrap could become more and more long to load at both server and worker start When this becomes a problem we will have to only import the latest workspace command or whatever For the moment this is not worth it the risk to import not the latest workspace command --- .../src/metadata/generated/schema.graphql | 82 ++-- .../src/metadata/generated/schema.ts | 234 +++++------ .../src/metadata/generated/types.ts | 376 +++++++++--------- packages/twenty-server/@types/jest.d.ts | 2 - .../commands/database-command.module.ts | 2 - .../upgrade-version-command.module.ts | 12 +- .../workspace-command-provider.module.ts | 14 + .../upgrade-command-registry.service.spec.ts | 24 +- .../upgrade-command-registry.service.ts | 17 - .../core-modules/upgrade/upgrade.module.ts | 4 + .../test/integration/utils/create-app.ts | 2 - .../test/integration/utils/setup-test.ts | 2 - 12 files changed, 372 insertions(+), 399 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 82b1f6fbab..c3ce0c8912 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -2193,30 +2193,18 @@ type DeletedWorkspaceMember { userWorkspaceId: UUID } -type BillingEntitlement { - key: BillingEntitlementKey! - value: Boolean! +type Relation { + type: RelationType! + sourceObjectMetadata: Object! + targetObjectMetadata: Object! + sourceFieldMetadata: Field! + targetFieldMetadata: Field! } -enum BillingEntitlementKey { - SSO - CUSTOM_DOMAIN - RLS - AUDIT_LOGS -} - -type DomainRecord { - validationType: String! - type: String! - status: String! - key: String! - value: String! -} - -type DomainValidRecords { - id: UUID! - domain: String! - records: [DomainRecord!]! +"""Relation type""" +enum RelationType { + ONE_TO_MANY + MANY_TO_ONE } type IndexEdge { @@ -2318,25 +2306,6 @@ type ObjectFieldsConnection { edges: [FieldEdge!]! } -type UpsertRowLevelPermissionPredicatesResult { - predicates: [RowLevelPermissionPredicate!]! - predicateGroups: [RowLevelPermissionPredicateGroup!]! -} - -type Relation { - type: RelationType! - sourceObjectMetadata: Object! - targetObjectMetadata: Object! - sourceFieldMetadata: Field! - targetFieldMetadata: Field! -} - -"""Relation type""" -enum RelationType { - ONE_TO_MANY - MANY_TO_ONE -} - type FieldConnection { """Paging information""" pageInfo: PageInfo! @@ -2345,6 +2314,37 @@ type FieldConnection { edges: [FieldEdge!]! } +type BillingEntitlement { + key: BillingEntitlementKey! + value: Boolean! +} + +enum BillingEntitlementKey { + SSO + CUSTOM_DOMAIN + RLS + AUDIT_LOGS +} + +type DomainRecord { + validationType: String! + type: String! + status: String! + key: String! + value: String! +} + +type DomainValidRecords { + id: UUID! + domain: String! + records: [DomainRecord!]! +} + +type UpsertRowLevelPermissionPredicatesResult { + predicates: [RowLevelPermissionPredicate!]! + predicateGroups: [RowLevelPermissionPredicateGroup!]! +} + type AuthToken { token: String! expiresAt: DateTime! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 0d3f7d1a14..ed967b572c 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1879,29 +1879,18 @@ export interface DeletedWorkspaceMember { __typename: 'DeletedWorkspaceMember' } -export interface BillingEntitlement { - key: BillingEntitlementKey - value: Scalars['Boolean'] - __typename: 'BillingEntitlement' +export interface Relation { + type: RelationType + sourceObjectMetadata: Object + targetObjectMetadata: Object + sourceFieldMetadata: Field + targetFieldMetadata: Field + __typename: 'Relation' } -export type BillingEntitlementKey = 'SSO' | 'CUSTOM_DOMAIN' | 'RLS' | 'AUDIT_LOGS' -export interface DomainRecord { - validationType: Scalars['String'] - type: Scalars['String'] - status: Scalars['String'] - key: Scalars['String'] - value: Scalars['String'] - __typename: 'DomainRecord' -} - -export interface DomainValidRecords { - id: Scalars['UUID'] - domain: Scalars['String'] - records: DomainRecord[] - __typename: 'DomainValidRecords' -} +/** Relation type */ +export type RelationType = 'ONE_TO_MANY' | 'MANY_TO_ONE' export interface IndexEdge { /** The node containing the Index */ @@ -2001,25 +1990,6 @@ export interface ObjectFieldsConnection { __typename: 'ObjectFieldsConnection' } -export interface UpsertRowLevelPermissionPredicatesResult { - predicates: RowLevelPermissionPredicate[] - predicateGroups: RowLevelPermissionPredicateGroup[] - __typename: 'UpsertRowLevelPermissionPredicatesResult' -} - -export interface Relation { - type: RelationType - sourceObjectMetadata: Object - targetObjectMetadata: Object - sourceFieldMetadata: Field - targetFieldMetadata: Field - __typename: 'Relation' -} - - -/** Relation type */ -export type RelationType = 'ONE_TO_MANY' | 'MANY_TO_ONE' - export interface FieldConnection { /** Paging information */ pageInfo: PageInfo @@ -2028,6 +1998,36 @@ export interface FieldConnection { __typename: 'FieldConnection' } +export interface BillingEntitlement { + key: BillingEntitlementKey + value: Scalars['Boolean'] + __typename: 'BillingEntitlement' +} + +export type BillingEntitlementKey = 'SSO' | 'CUSTOM_DOMAIN' | 'RLS' | 'AUDIT_LOGS' + +export interface DomainRecord { + validationType: Scalars['String'] + type: Scalars['String'] + status: Scalars['String'] + key: Scalars['String'] + value: Scalars['String'] + __typename: 'DomainRecord' +} + +export interface DomainValidRecords { + id: Scalars['UUID'] + domain: Scalars['String'] + records: DomainRecord[] + __typename: 'DomainValidRecords' +} + +export interface UpsertRowLevelPermissionPredicatesResult { + predicates: RowLevelPermissionPredicate[] + predicateGroups: RowLevelPermissionPredicateGroup[] + __typename: 'UpsertRowLevelPermissionPredicatesResult' +} + export interface AuthToken { token: Scalars['String'] expiresAt: Scalars['DateTime'] @@ -5149,27 +5149,12 @@ export interface DeletedWorkspaceMemberGenqlSelection{ __scalar?: boolean | number } -export interface BillingEntitlementGenqlSelection{ - key?: boolean | number - value?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface DomainRecordGenqlSelection{ - validationType?: boolean | number +export interface RelationGenqlSelection{ type?: boolean | number - status?: boolean | number - key?: boolean | number - value?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface DomainValidRecordsGenqlSelection{ - id?: boolean | number - domain?: boolean | number - records?: DomainRecordGenqlSelection + sourceObjectMetadata?: ObjectGenqlSelection + targetObjectMetadata?: ObjectGenqlSelection + sourceFieldMetadata?: FieldGenqlSelection + targetFieldMetadata?: FieldGenqlSelection __typename?: boolean | number __scalar?: boolean | number } @@ -5284,23 +5269,6 @@ export interface ObjectFieldsConnectionGenqlSelection{ __scalar?: boolean | number } -export interface UpsertRowLevelPermissionPredicatesResultGenqlSelection{ - predicates?: RowLevelPermissionPredicateGenqlSelection - predicateGroups?: RowLevelPermissionPredicateGroupGenqlSelection - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface RelationGenqlSelection{ - type?: boolean | number - sourceObjectMetadata?: ObjectGenqlSelection - targetObjectMetadata?: ObjectGenqlSelection - sourceFieldMetadata?: FieldGenqlSelection - targetFieldMetadata?: FieldGenqlSelection - __typename?: boolean | number - __scalar?: boolean | number -} - export interface FieldConnectionGenqlSelection{ /** Paging information */ pageInfo?: PageInfoGenqlSelection @@ -5310,6 +5278,38 @@ export interface FieldConnectionGenqlSelection{ __scalar?: boolean | number } +export interface BillingEntitlementGenqlSelection{ + key?: boolean | number + value?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface DomainRecordGenqlSelection{ + validationType?: boolean | number + type?: boolean | number + status?: boolean | number + key?: boolean | number + value?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface DomainValidRecordsGenqlSelection{ + id?: boolean | number + domain?: boolean | number + records?: DomainRecordGenqlSelection + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface UpsertRowLevelPermissionPredicatesResultGenqlSelection{ + predicates?: RowLevelPermissionPredicateGenqlSelection + predicateGroups?: RowLevelPermissionPredicateGroupGenqlSelection + __typename?: boolean | number + __scalar?: boolean | number +} + export interface AuthTokenGenqlSelection{ token?: boolean | number expiresAt?: boolean | number @@ -8232,26 +8232,10 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null - const BillingEntitlement_possibleTypes: string[] = ['BillingEntitlement'] - export const isBillingEntitlement = (obj?: { __typename?: any } | null): obj is BillingEntitlement => { - if (!obj?.__typename) throw new Error('__typename is missing in "isBillingEntitlement"') - return BillingEntitlement_possibleTypes.includes(obj.__typename) - } - - - - const DomainRecord_possibleTypes: string[] = ['DomainRecord'] - export const isDomainRecord = (obj?: { __typename?: any } | null): obj is DomainRecord => { - if (!obj?.__typename) throw new Error('__typename is missing in "isDomainRecord"') - return DomainRecord_possibleTypes.includes(obj.__typename) - } - - - - const DomainValidRecords_possibleTypes: string[] = ['DomainValidRecords'] - export const isDomainValidRecords = (obj?: { __typename?: any } | null): obj is DomainValidRecords => { - if (!obj?.__typename) throw new Error('__typename is missing in "isDomainValidRecords"') - return DomainValidRecords_possibleTypes.includes(obj.__typename) + const Relation_possibleTypes: string[] = ['Relation'] + export const isRelation = (obj?: { __typename?: any } | null): obj is Relation => { + if (!obj?.__typename) throw new Error('__typename is missing in "isRelation"') + return Relation_possibleTypes.includes(obj.__typename) } @@ -8352,22 +8336,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null - const UpsertRowLevelPermissionPredicatesResult_possibleTypes: string[] = ['UpsertRowLevelPermissionPredicatesResult'] - export const isUpsertRowLevelPermissionPredicatesResult = (obj?: { __typename?: any } | null): obj is UpsertRowLevelPermissionPredicatesResult => { - if (!obj?.__typename) throw new Error('__typename is missing in "isUpsertRowLevelPermissionPredicatesResult"') - return UpsertRowLevelPermissionPredicatesResult_possibleTypes.includes(obj.__typename) - } - - - - const Relation_possibleTypes: string[] = ['Relation'] - export const isRelation = (obj?: { __typename?: any } | null): obj is Relation => { - if (!obj?.__typename) throw new Error('__typename is missing in "isRelation"') - return Relation_possibleTypes.includes(obj.__typename) - } - - - const FieldConnection_possibleTypes: string[] = ['FieldConnection'] export const isFieldConnection = (obj?: { __typename?: any } | null): obj is FieldConnection => { if (!obj?.__typename) throw new Error('__typename is missing in "isFieldConnection"') @@ -8376,6 +8344,38 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const BillingEntitlement_possibleTypes: string[] = ['BillingEntitlement'] + export const isBillingEntitlement = (obj?: { __typename?: any } | null): obj is BillingEntitlement => { + if (!obj?.__typename) throw new Error('__typename is missing in "isBillingEntitlement"') + return BillingEntitlement_possibleTypes.includes(obj.__typename) + } + + + + const DomainRecord_possibleTypes: string[] = ['DomainRecord'] + export const isDomainRecord = (obj?: { __typename?: any } | null): obj is DomainRecord => { + if (!obj?.__typename) throw new Error('__typename is missing in "isDomainRecord"') + return DomainRecord_possibleTypes.includes(obj.__typename) + } + + + + const DomainValidRecords_possibleTypes: string[] = ['DomainValidRecords'] + export const isDomainValidRecords = (obj?: { __typename?: any } | null): obj is DomainValidRecords => { + if (!obj?.__typename) throw new Error('__typename is missing in "isDomainValidRecords"') + return DomainValidRecords_possibleTypes.includes(obj.__typename) + } + + + + const UpsertRowLevelPermissionPredicatesResult_possibleTypes: string[] = ['UpsertRowLevelPermissionPredicatesResult'] + export const isUpsertRowLevelPermissionPredicatesResult = (obj?: { __typename?: any } | null): obj is UpsertRowLevelPermissionPredicatesResult => { + if (!obj?.__typename) throw new Error('__typename is missing in "isUpsertRowLevelPermissionPredicatesResult"') + return UpsertRowLevelPermissionPredicatesResult_possibleTypes.includes(obj.__typename) + } + + + 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"') @@ -9576,6 +9576,11 @@ export const enumQueueMetricsTimeRange = { OneHour: 'OneHour' as const } +export const enumRelationType = { + ONE_TO_MANY: 'ONE_TO_MANY' as const, + MANY_TO_ONE: 'MANY_TO_ONE' as const +} + export const enumBillingEntitlementKey = { SSO: 'SSO' as const, CUSTOM_DOMAIN: 'CUSTOM_DOMAIN' as const, @@ -9583,11 +9588,6 @@ export const enumBillingEntitlementKey = { AUDIT_LOGS: 'AUDIT_LOGS' as const } -export const enumRelationType = { - ONE_TO_MANY: 'ONE_TO_MANY' as const, - MANY_TO_ONE: 'MANY_TO_ONE' as const -} - export const enumEmailingDomainDriver = { AWS_SES: 'AWS_SES' as const } diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 04c9a41b04..3a08a23d41 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -63,7 +63,7 @@ export default { 209, 221, 238, - 255, + 253, 292, 293, 303, @@ -789,10 +789,10 @@ export default { 3 ], "relation": [ - 254 + 237 ], "morphRelations": [ - 254 + 237 ], "object": [ 46 @@ -851,7 +851,7 @@ export default { 36 ], "objectMetadata": [ - 247, + 245, { "paging": [ 39, @@ -864,7 +864,7 @@ export default { } ], "indexFieldMetadatas": [ - 245, + 243, { "paging": [ 39, @@ -1109,7 +1109,7 @@ export default { 37 ], "fields": [ - 252, + 250, { "paging": [ 39, @@ -1122,7 +1122,7 @@ export default { } ], "indexMetadatas": [ - 250, + 248, { "paging": [ 39, @@ -1703,7 +1703,7 @@ export default { 131 ], "billingEntitlements": [ - 237 + 252 ], "hasValidEnterpriseKey": [ 6 @@ -4419,9 +4419,179 @@ export default { 1 ] }, + "Relation": { + "type": [ + 238 + ], + "sourceObjectMetadata": [ + 46 + ], + "targetObjectMetadata": [ + 46 + ], + "sourceFieldMetadata": [ + 34 + ], + "targetFieldMetadata": [ + 34 + ], + "__typename": [ + 1 + ] + }, + "RelationType": {}, + "IndexEdge": { + "node": [ + 37 + ], + "cursor": [ + 40 + ], + "__typename": [ + 1 + ] + }, + "PageInfo": { + "hasNextPage": [ + 6 + ], + "hasPreviousPage": [ + 6 + ], + "startCursor": [ + 40 + ], + "endCursor": [ + 40 + ], + "__typename": [ + 1 + ] + }, + "IndexConnection": { + "pageInfo": [ + 240 + ], + "edges": [ + 239 + ], + "__typename": [ + 1 + ] + }, + "IndexFieldEdge": { + "node": [ + 36 + ], + "cursor": [ + 40 + ], + "__typename": [ + 1 + ] + }, + "IndexIndexFieldMetadatasConnection": { + "pageInfo": [ + 240 + ], + "edges": [ + 242 + ], + "__typename": [ + 1 + ] + }, + "ObjectEdge": { + "node": [ + 46 + ], + "cursor": [ + 40 + ], + "__typename": [ + 1 + ] + }, + "IndexObjectMetadataConnection": { + "pageInfo": [ + 240 + ], + "edges": [ + 244 + ], + "__typename": [ + 1 + ] + }, + "ObjectRecordCount": { + "objectNamePlural": [ + 1 + ], + "totalCount": [ + 21 + ], + "__typename": [ + 1 + ] + }, + "ObjectConnection": { + "pageInfo": [ + 240 + ], + "edges": [ + 244 + ], + "__typename": [ + 1 + ] + }, + "ObjectIndexMetadatasConnection": { + "pageInfo": [ + 240 + ], + "edges": [ + 239 + ], + "__typename": [ + 1 + ] + }, + "FieldEdge": { + "node": [ + 34 + ], + "cursor": [ + 40 + ], + "__typename": [ + 1 + ] + }, + "ObjectFieldsConnection": { + "pageInfo": [ + 240 + ], + "edges": [ + 249 + ], + "__typename": [ + 1 + ] + }, + "FieldConnection": { + "pageInfo": [ + 240 + ], + "edges": [ + 249 + ], + "__typename": [ + 1 + ] + }, "BillingEntitlement": { "key": [ - 238 + 253 ], "value": [ 6 @@ -4459,145 +4629,7 @@ export default { 1 ], "records": [ - 239 - ], - "__typename": [ - 1 - ] - }, - "IndexEdge": { - "node": [ - 37 - ], - "cursor": [ - 40 - ], - "__typename": [ - 1 - ] - }, - "PageInfo": { - "hasNextPage": [ - 6 - ], - "hasPreviousPage": [ - 6 - ], - "startCursor": [ - 40 - ], - "endCursor": [ - 40 - ], - "__typename": [ - 1 - ] - }, - "IndexConnection": { - "pageInfo": [ - 242 - ], - "edges": [ - 241 - ], - "__typename": [ - 1 - ] - }, - "IndexFieldEdge": { - "node": [ - 36 - ], - "cursor": [ - 40 - ], - "__typename": [ - 1 - ] - }, - "IndexIndexFieldMetadatasConnection": { - "pageInfo": [ - 242 - ], - "edges": [ - 244 - ], - "__typename": [ - 1 - ] - }, - "ObjectEdge": { - "node": [ - 46 - ], - "cursor": [ - 40 - ], - "__typename": [ - 1 - ] - }, - "IndexObjectMetadataConnection": { - "pageInfo": [ - 242 - ], - "edges": [ - 246 - ], - "__typename": [ - 1 - ] - }, - "ObjectRecordCount": { - "objectNamePlural": [ - 1 - ], - "totalCount": [ - 21 - ], - "__typename": [ - 1 - ] - }, - "ObjectConnection": { - "pageInfo": [ - 242 - ], - "edges": [ - 246 - ], - "__typename": [ - 1 - ] - }, - "ObjectIndexMetadatasConnection": { - "pageInfo": [ - 242 - ], - "edges": [ - 241 - ], - "__typename": [ - 1 - ] - }, - "FieldEdge": { - "node": [ - 34 - ], - "cursor": [ - 40 - ], - "__typename": [ - 1 - ] - }, - "ObjectFieldsConnection": { - "pageInfo": [ - 242 - ], - "edges": [ - 251 + 254 ], "__typename": [ 1 @@ -4614,38 +4646,6 @@ export default { 1 ] }, - "Relation": { - "type": [ - 255 - ], - "sourceObjectMetadata": [ - 46 - ], - "targetObjectMetadata": [ - 46 - ], - "sourceFieldMetadata": [ - 34 - ], - "targetFieldMetadata": [ - 34 - ], - "__typename": [ - 1 - ] - }, - "RelationType": {}, - "FieldConnection": { - "pageInfo": [ - 242 - ], - "edges": [ - 251 - ], - "__typename": [ - 1 - ] - }, "AuthToken": { "token": [ 1 @@ -5975,7 +5975,7 @@ export default { }, "AgentChatThreadConnection": { "pageInfo": [ - 242 + 240 ], "edges": [ 335 @@ -6582,7 +6582,7 @@ export default { } ], "objectRecordCounts": [ - 248 + 246 ], "object": [ 46, @@ -6594,7 +6594,7 @@ export default { } ], "objects": [ - 249, + 247, { "paging": [ 39, @@ -6616,7 +6616,7 @@ export default { } ], "indexMetadatas": [ - 243, + 241, { "paging": [ 39, @@ -6665,7 +6665,7 @@ export default { } ], "fields": [ - 256, + 251, { "paging": [ 39, @@ -8340,7 +8340,7 @@ export default { } ], "upsertRowLevelPermissionPredicates": [ - 253, + 256, { "input": [ 456, @@ -9082,7 +9082,7 @@ export default { 66 ], "checkCustomDomainValidRecords": [ - 240 + 255 ], "createOIDCIdentityProvider": [ 232, @@ -9415,7 +9415,7 @@ export default { } ], "checkPublicDomainValidRecords": [ - 240, + 255, { "domain": [ 1, diff --git a/packages/twenty-server/@types/jest.d.ts b/packages/twenty-server/@types/jest.d.ts index 69ac0fd354..d476966d04 100644 --- a/packages/twenty-server/@types/jest.d.ts +++ b/packages/twenty-server/@types/jest.d.ts @@ -3,7 +3,6 @@ import { type INestApplication } from '@nestjs/common'; import 'jest'; import { type DataSource } from 'typeorm'; -import { type DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; declare module '@jest/types' { @@ -36,7 +35,6 @@ declare global { var testDataSource: DataSource; var app: INestApplication; var dataSourceService: DataSourceService; - var dataSeedWorkspaceCommand: DataSeedWorkspaceCommand; } export {}; diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts index c36907285a..31d898e0b4 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -8,7 +8,6 @@ import { InstanceCommandGenerationService } from 'src/database/commands/instance import { ListOrphanedWorkspaceEntitiesCommand } from 'src/database/commands/list-and-delete-orphaned-workspace-entities.command'; import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question'; import { RunInstanceCommandsCommand } from 'src/database/commands/run-instance-commands.command'; -import { InstanceCommandProviderModule } from 'src/database/commands/upgrade-version-command/instance-command-provider.module'; import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/upgrade-version-command.module'; import { WorkspaceExportModule } from 'src/database/commands/workspace-export/workspace-export.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; @@ -45,7 +44,6 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au @Module({ imports: [ UpgradeVersionCommandModule, - InstanceCommandProviderModule, TypeOrmModule.forFeature([WorkspaceEntity]), WorkspaceExportModule, // Cron command dependencies diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts index 3f82e85373..4b00b71037 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts @@ -1,22 +1,12 @@ import { Module } from '@nestjs/common'; import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; -import { V1_21_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-21/1-21-upgrade-version-command.module'; -import { V1_22_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-22/1-22-upgrade-version-command.module'; -import { V1_23_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-23/1-23-upgrade-version-command.module'; import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command'; import { UpgradeModule } from 'src/engine/core-modules/upgrade/upgrade.module'; import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module'; @Module({ - imports: [ - V1_21_UpgradeVersionCommandModule, - V1_22_UpgradeVersionCommandModule, - V1_23_UpgradeVersionCommandModule, - UpgradeModule, - WorkspaceVersionModule, - WorkspaceIteratorModule, - ], + imports: [UpgradeModule, WorkspaceVersionModule, WorkspaceIteratorModule], providers: [UpgradeCommand], }) export class UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts new file mode 100644 index 0000000000..c8e6518fc5 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; + +import { V1_21_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-21/1-21-upgrade-version-command.module'; +import { V1_22_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-22/1-22-upgrade-version-command.module'; +import { V1_23_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-23/1-23-upgrade-version-command.module'; + +@Module({ + imports: [ + V1_21_UpgradeVersionCommandModule, + V1_22_UpgradeVersionCommandModule, + V1_23_UpgradeVersionCommandModule, + ], +}) +export class WorkspaceCommandProviderModule {} diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-command-registry.service.spec.ts b/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-command-registry.service.spec.ts index eef3b2bfb7..359a166dfe 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-command-registry.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-command-registry.service.spec.ts @@ -157,24 +157,14 @@ describe('UpgradeCommandRegistryService', () => { ); }); - it('should return empty array for version with no commands', async () => { - const service = await buildRegistryService([]); - - const bundleB = service.getBundleForVersion(VERSION_B); - const bundleA = service.getBundleForVersion(VERSION_A); - - expect(bundleB.fastInstanceCommands).toStrictEqual([]); - expect(bundleA.fastInstanceCommands).toStrictEqual([]); - expect(bundleB.workspaceCommands).toStrictEqual([]); - expect(bundleA.workspaceCommands).toStrictEqual([]); - }); - - it('should not throw when no commands are discovered (empty bundle)', async () => { - await expect(buildRegistryService([])).resolves.toBeDefined(); + it('should throw when no workspace commands are discovered', async () => { + await expect(buildRegistryService([])).rejects.toThrow( + 'Upgrade sequence must contain at least one workspace command', + ); }); it('should return empty array for unsupported version', async () => { - const service = await buildRegistryService([]); + const service = await buildRegistryService([new WorkspaceCommandA()]); expect( service.getBundleForVersion('99.0.0' as typeof VERSION_A) @@ -284,8 +274,8 @@ describe('UpgradeCommandRegistryService', () => { ]); }); - it('should return empty array from getCrossUpgradeSupportedFastInstanceCommands when no commands registered', async () => { - const service = await buildRegistryService([]); + it('should return empty array from getCrossUpgradeSupportedFastInstanceCommands when no instance commands registered', async () => { + const service = await buildRegistryService([new WorkspaceCommandA()]); expect( service.getCrossUpgradeSupportedFastInstanceCommands(), diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-command-registry.service.ts b/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-command-registry.service.ts index b6261dccb4..ea1dbcc26a 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-command-registry.service.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-command-registry.service.ts @@ -247,33 +247,16 @@ export class UpgradeCommandRegistryService implements OnModuleInit { } private validateAtLeastOneVersionBundleHasWorkspaceCommands(): void { - let totalCommandCount = 0; let hasWorkspaceCommands = false; for (const version of TWENTY_CROSS_UPGRADE_SUPPORTED_VERSIONS) { const bundle = this.getBundleForVersion(version); - totalCommandCount += - bundle.fastInstanceCommands.length + - bundle.slowInstanceCommands.length + - bundle.workspaceCommands.length; - if (bundle.workspaceCommands.length > 0) { hasWorkspaceCommands = true; } } - // UpgradeModule is loaded in the worker transitively via WorkspaceModule, - // but no command modules are imported — zero providers are discovered. - // TODO: split WorkspaceModule so the worker doesn't pull in UpgradeModule - if (totalCommandCount === 0) { - this.logger.warn( - 'No upgrade commands discovered — skipping workspace command validation', - ); - - return; - } - if (!hasWorkspaceCommands) { throw new Error( 'Upgrade sequence must contain at least one workspace command', diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/upgrade.module.ts b/packages/twenty-server/src/engine/core-modules/upgrade/upgrade.module.ts index 1f7fea8523..fbdc70831b 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/upgrade.module.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/upgrade.module.ts @@ -3,6 +3,8 @@ import { DiscoveryModule } from '@nestjs/core'; import { TypeOrmModule } from '@nestjs/typeorm'; import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; +import { InstanceCommandProviderModule } from 'src/database/commands/upgrade-version-command/instance-command-provider.module'; +import { WorkspaceCommandProviderModule } from 'src/database/commands/upgrade-version-command/workspace-command-provider.module'; import { InstanceCommandRunnerService } from 'src/engine/core-modules/upgrade/services/instance-command-runner.service'; import { UpgradeCommandRegistryService } from 'src/engine/core-modules/upgrade/services/upgrade-command-registry.service'; import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service'; @@ -16,6 +18,8 @@ import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-v @Module({ imports: [ DiscoveryModule, + InstanceCommandProviderModule, + WorkspaceCommandProviderModule, WorkspaceIteratorModule, WorkspaceVersionModule, TypeOrmModule.forFeature([UpgradeMigrationEntity, WorkspaceEntity]), diff --git a/packages/twenty-server/test/integration/utils/create-app.ts b/packages/twenty-server/test/integration/utils/create-app.ts index 3b8b725f18..27b932c2db 100644 --- a/packages/twenty-server/test/integration/utils/create-app.ts +++ b/packages/twenty-server/test/integration/utils/create-app.ts @@ -10,7 +10,6 @@ import bytes from 'bytes'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from 'src/app.module'; -import { CommandModule } from 'src/command/command.module'; import { settings } from 'src/engine/constants/settings'; import { StripeSDKMockService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/mocks/stripe-sdk-mock.service'; import { StripeSDKService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/services/stripe-sdk.service'; @@ -52,7 +51,6 @@ export const createApp = async ( let moduleBuilder: TestingModuleBuilder = Test.createTestingModule({ imports: [ AppModule, - CommandModule, JobsModule, MessageQueueModule.registerExplorer(), ], diff --git a/packages/twenty-server/test/integration/utils/setup-test.ts b/packages/twenty-server/test/integration/utils/setup-test.ts index 8432d735f6..535266c976 100644 --- a/packages/twenty-server/test/integration/utils/setup-test.ts +++ b/packages/twenty-server/test/integration/utils/setup-test.ts @@ -1,7 +1,6 @@ import { type JestConfigWithTsJest } from 'ts-jest'; import 'tsconfig-paths/register'; -import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource'; import { createApp } from './create-app'; @@ -19,5 +18,4 @@ export default async (_: unknown, projectConfig: JestConfigWithTsJest) => { global.app = app; global.testDataSource = rawDataSource; - global.dataSeedWorkspaceCommand = app.get(DataSeedWorkspaceCommand); };