Files
twenty/packages/twenty-server/src/database/commands/run-instance-commands.command.ts
T
Paul RastoinandGitHub 2413af0ba9 [run-instance-commands] Preserve fast slow sequentiality (#19757)
# Introduction
The command was wrongly running all fast and then all slow ignoring
instance commands segment
Leading to 
```ts
1.23.0_AddGlobalObjectContextToCommandMenuItemAvailabilityTypeFastInstanceCommand_1776090711153 executed successfully
[Nest] 32679  - 04/16/2026, 1:21:07 PM     LOG [InstanceCommandRunnerService] 1.23.0_DropWorkspaceVersionColumnFastInstanceCommand_1785000000000 executed successfully
[Nest] 32679  - 04/16/2026, 1:21:07 PM     LOG [InstanceCommandRunnerService] 1.22.0_BackfillWorkspaceIdOnIndirectEntitiesSlowInstanceCommand_1775758621018 executed successfully
[Nest] 32679  - 04/16/2026, 1:21:07 PM     LOG [RunInstanceCommandsCommand] Instance commands completed
```

No prod/self host impact as 1.23 hasn't been released yet
2026-04-16 12:14:47 +00:00

170 lines
5.4 KiB
TypeScript

import { Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import chalk from 'chalk';
import { Command, CommandRunner, Option } from 'nest-commander';
import { DataSource } from 'typeorm';
import { TWENTY_PREVIOUS_VERSIONS } from 'src/engine/core-modules/upgrade/constants/twenty-previous-versions.constant';
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';
import { UpgradeSequenceReaderService } from 'src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
type RunInstanceCommandsOptions = {
force?: boolean;
includeSlow?: boolean;
};
// TODO should be replaced by a specific call to the upgrade
@Command({
name: 'run-instance-commands',
description:
'Run legacy TypeORM migrations and all registered instance commands',
})
export class RunInstanceCommandsCommand extends CommandRunner {
private readonly logger = new Logger(RunInstanceCommandsCommand.name);
constructor(
@InjectDataSource()
private readonly dataSource: DataSource,
private readonly workspaceVersionService: WorkspaceVersionService,
private readonly upgradeCommandRegistryService: UpgradeCommandRegistryService,
private readonly upgradeSequenceReaderService: UpgradeSequenceReaderService,
private readonly instanceUpgradeService: InstanceCommandRunnerService,
private readonly upgradeMigrationService: UpgradeMigrationService,
) {
super();
}
@Option({
flags: '-f, --force',
description: 'Skip workspace version safety check',
required: false,
})
parseForce(): boolean {
return true;
}
@Option({
flags: '--include-slow',
description: 'Also run slow instance commands (data migration + DDL)',
required: false,
})
parseIncludeSlow(): boolean {
return true;
}
async run(
_passedParams: string[],
options: RunInstanceCommandsOptions,
): Promise<void> {
try {
await this.checkWorkspaceVersionSafety(options);
await this.runLegacyPendingTypeOrmMigrations();
const activeOrSuspendedWorkspaceIds =
await this.workspaceVersionService.getActiveOrSuspendedWorkspaceIds();
const sequence = this.upgradeSequenceReaderService.getUpgradeSequence();
for (const step of sequence) {
if (step.kind === 'fast-instance') {
const result =
await this.instanceUpgradeService.runFastInstanceCommand({
command: step.command,
name: step.name,
});
if (result.status === 'failed') {
throw result.error;
}
}
if (step.kind === 'slow-instance' && options.includeSlow) {
const result =
await this.instanceUpgradeService.runSlowInstanceCommand({
command: step.command,
name: step.name,
skipDataMigration: activeOrSuspendedWorkspaceIds.length === 0,
});
if (result.status === 'failed') {
throw result.error;
}
}
}
this.logger.log(chalk.green('Instance commands completed'));
} catch (error) {
this.logger.error(
chalk.red(`Instance commands failed: ${error.message}`),
);
throw error;
}
}
private async checkWorkspaceVersionSafety(
options: RunInstanceCommandsOptions,
): Promise<void> {
if (options.force) {
this.logger.warn(
chalk.yellow('Skipping workspace version check (--force flag used)'),
);
return;
}
const activeOrSuspendedWorkspaceIds =
await this.workspaceVersionService.getActiveOrSuspendedWorkspaceIds();
if (activeOrSuspendedWorkspaceIds.length === 0) {
return;
}
const previousVersion =
TWENTY_PREVIOUS_VERSIONS[TWENTY_PREVIOUS_VERSIONS.length - 1];
const lastWorkspaceCommand =
this.upgradeCommandRegistryService.getLastWorkspaceCommandForVersion(
previousVersion,
);
if (!lastWorkspaceCommand) {
return;
}
const allAtPreviousVersion =
await this.upgradeMigrationService.areAllWorkspacesAtCommand({
commandName: lastWorkspaceCommand.name,
workspaceIds: activeOrSuspendedWorkspaceIds,
});
if (!allAtPreviousVersion) {
throw new Error(
'Unable to run instance commands. Some workspace(s) have not completed ' +
`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).',
);
}
}
private async runLegacyPendingTypeOrmMigrations(): Promise<void> {
this.logger.log('Running legacy TypeORM migrations...');
const migrations = await this.dataSource.runMigrations({
transaction: 'each',
});
if (migrations.length === 0) {
this.logger.log('No pending legacy migrations');
} else {
this.logger.log(
`Executed ${migrations.length} legacy migration(s): ${migrations.map((migration) => migration.name).join(', ')}`,
);
}
}
}