diff --git a/packages/twenty-server/src/database/commands/run-instance-commands.command.ts b/packages/twenty-server/src/database/commands/run-instance-commands.command.ts index 0db1dca6541..c558378780a 100644 --- a/packages/twenty-server/src/database/commands/run-instance-commands.command.ts +++ b/packages/twenty-server/src/database/commands/run-instance-commands.command.ts @@ -135,15 +135,29 @@ export class RunInstanceCommandsCommand extends CommandRunner { return; } - const allAtPreviousVersion = - await this.upgradeMigrationService.areAllWorkspacesAtCommand({ - commandName: lastWorkspaceCommand.name, - workspaceIds: activeOrSuspendedWorkspaceIds, - }); + const workspaceCursors = + await this.upgradeMigrationService.getWorkspaceLastAttemptedCommandNameOrThrow( + activeOrSuspendedWorkspaceIds, + ); + + const allAtPreviousVersion = activeOrSuspendedWorkspaceIds.every( + (workspaceId) => { + const cursor = workspaceCursors.get(workspaceId); + + if (!cursor) { + return false; + } + + return this.upgradeSequenceReaderService.isStepCompletedOrPassed({ + cursor, + stepName: lastWorkspaceCommand.name, + }); + }, + ); if (!allAtPreviousVersion) { throw new Error( - 'Unable to run instance commands. Some workspace(s) have not completed ' + + 'Unable to run instance commands. Some workspace(s) have not reached ' + `the last workspace command for ${previousVersion} ("${lastWorkspaceCommand.name}").\n` + 'Please ensure all workspaces are upgraded to at least the previous version before running migrations.\n' + 'Use --force to bypass this check (not recommended).', diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-sequence-reader.service.spec.ts b/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-sequence-reader.service.spec.ts index 08ab58770bf..c6d28f9da61 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-sequence-reader.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/services/__tests__/upgrade-sequence-reader.service.spec.ts @@ -74,6 +74,77 @@ const makeFastInstance = (name: string) => makeStep('fast-instance', name); const makeWorkspace = (name: string) => makeStep('workspace', name); describe('UpgradeSequenceReaderService', () => { + describe('isStepCompletedOrPassed', () => { + it('should return true when cursor is on required step with completed status', async () => { + const sequence = [ + makeFastInstance('Ic0'), + makeWorkspace('Wc0'), + makeWorkspace('Wc1'), + ]; + + const service = await buildServiceWithMockedSequence(sequence); + + const result = service.isStepCompletedOrPassed({ + cursor: { name: 'Wc1', status: 'completed' }, + stepName: 'Wc1', + }); + + expect(result).toBe(true); + }); + + it('should return false when cursor is on required step with failed status', async () => { + const sequence = [ + makeFastInstance('Ic0'), + makeWorkspace('Wc0'), + makeWorkspace('Wc1'), + ]; + + const service = await buildServiceWithMockedSequence(sequence); + + const result = service.isStepCompletedOrPassed({ + cursor: { name: 'Wc1', status: 'failed' }, + stepName: 'Wc1', + }); + + expect(result).toBe(false); + }); + + it('should return true when cursor is after required step', async () => { + const sequence = [ + makeFastInstance('Ic0'), + makeWorkspace('Wc0'), + makeWorkspace('Wc1'), + makeFastInstance('Ic1'), + ]; + + const service = await buildServiceWithMockedSequence(sequence); + + const result = service.isStepCompletedOrPassed({ + cursor: { name: 'Ic1', status: 'failed' }, + stepName: 'Wc1', + }); + + expect(result).toBe(true); + }); + + it('should return false when cursor is before required step', async () => { + const sequence = [ + makeFastInstance('Ic0'), + makeWorkspace('Wc0'), + makeWorkspace('Wc1'), + ]; + + const service = await buildServiceWithMockedSequence(sequence); + + const result = service.isStepCompletedOrPassed({ + cursor: { name: 'Wc0', status: 'completed' }, + stepName: 'Wc1', + }); + + expect(result).toBe(false); + }); + }); + describe('getInitialCursorForNewWorkspace', () => { it('should return last workspace command of segment following completed instance command', async () => { const sequence = [ diff --git a/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service.ts b/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service.ts index 846c15cf163..fb7ad00d028 100644 --- a/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service.ts +++ b/packages/twenty-server/src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service.ts @@ -162,6 +162,35 @@ export class UpgradeSequenceReaderService { : workspaceCommands.slice(cursorIndex); } + isStepCompletedOrPassed({ + cursor, + stepName, + }: { + cursor: { name: string; status: UpgradeMigrationStatus }; + stepName: string; + }): boolean { + const sequence = this.getUpgradeSequence(); + + const cursorIndex = this.locateStepInSequenceOrThrow({ + sequence, + stepName: cursor.name, + }); + const stepIndex = this.locateStepInSequenceOrThrow({ + sequence, + stepName, + }); + + if (cursorIndex > stepIndex) { + return true; + } + + if (cursorIndex < stepIndex) { + return false; + } + + return cursor.status === 'completed'; + } + getInitialCursorForNewWorkspace(lastAttemptedInstanceCommand: { name: string; status: UpgradeMigrationStatus;