Refactor upgrade commands (#10592)
Simplifying a lot the upgrade system.
New way to upgrade:
`yarn command:prod upgrade`
New way to write upgrade commands (all wrapping is done for you)
```
override async runOnWorkspace({
index,
total,
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {}
```
Also cleaning CommandModule imports to make it lighter
This commit is contained in:
@ -0,0 +1,156 @@
|
||||
import chalk from 'chalk';
|
||||
import { Option } from 'nest-commander';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared';
|
||||
import { In, MoreThanOrEqual, Repository } from 'typeorm';
|
||||
|
||||
import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
export type ActiveOrSuspendedWorkspacesMigrationCommandOptions = {
|
||||
workspaceIds: string[];
|
||||
startFromWorkspaceId?: string;
|
||||
workspaceCountLimit?: number;
|
||||
dryRun?: boolean;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export type RunOnWorkspaceArgs = {
|
||||
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions;
|
||||
workspaceId: string;
|
||||
dataSource: WorkspaceDataSource;
|
||||
index: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner<
|
||||
Options extends
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandOptions = ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||
> extends MigrationCommandRunner {
|
||||
private workspaceIds: string[] = [];
|
||||
private startFromWorkspaceId: string | undefined;
|
||||
private workspaceCountLimit: number | undefined;
|
||||
|
||||
constructor(
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--start-from-workspace-id [workspace_id]',
|
||||
description:
|
||||
'Start from a specific workspace id. Workspaces are processed in ascending order of id.',
|
||||
required: false,
|
||||
})
|
||||
parseStartFromWorkspaceId(val: string): string {
|
||||
this.startFromWorkspaceId = val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--workspace-count-limit [count]',
|
||||
description:
|
||||
'Limit the number of workspaces to process. Workspaces are processed in ascending order of id.',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceCountLimit(val: string): number {
|
||||
this.workspaceCountLimit = parseInt(val);
|
||||
|
||||
if (isNaN(this.workspaceCountLimit)) {
|
||||
throw new Error('Workspace count limit must be a number');
|
||||
}
|
||||
|
||||
if (this.workspaceCountLimit <= 0) {
|
||||
throw new Error('Workspace count limit must be greater than 0');
|
||||
}
|
||||
|
||||
return this.workspaceCountLimit;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description:
|
||||
'workspace id. Command runs on all active workspaces if not provided.',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceId(val: string): string[] {
|
||||
this.workspaceIds.push(val);
|
||||
|
||||
return this.workspaceIds;
|
||||
}
|
||||
|
||||
protected async fetchActiveWorkspaceIds(): Promise<string[]> {
|
||||
const activeWorkspaces = await this.workspaceRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
activationStatus: In([
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
]),
|
||||
...(this.startFromWorkspaceId
|
||||
? { id: MoreThanOrEqual(this.startFromWorkspaceId) }
|
||||
: {}),
|
||||
},
|
||||
order: {
|
||||
id: 'ASC',
|
||||
},
|
||||
take: this.workspaceCountLimit,
|
||||
});
|
||||
|
||||
return activeWorkspaces.map((workspace) => workspace.id);
|
||||
}
|
||||
|
||||
override async runMigrationCommand(
|
||||
_passedParams: string[],
|
||||
options: Options,
|
||||
): Promise<void> {
|
||||
const activeWorkspaceIds =
|
||||
this.workspaceIds.length > 0
|
||||
? this.workspaceIds
|
||||
: await this.fetchActiveWorkspaceIds();
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
|
||||
}
|
||||
|
||||
try {
|
||||
for (const [index, workspaceId] of activeWorkspaceIds.entries()) {
|
||||
this.logger.log(
|
||||
`Running command on workspace ${workspaceId} ${index + 1}/${activeWorkspaceIds.length}`,
|
||||
);
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
workspaceId,
|
||||
false,
|
||||
);
|
||||
|
||||
try {
|
||||
await this.runOnWorkspace({
|
||||
options,
|
||||
workspaceId,
|
||||
dataSource,
|
||||
index: index,
|
||||
total: activeWorkspaceIds.length,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
chalk.red(`Error in workspace ${workspaceId}: ${error.message}`),
|
||||
);
|
||||
}
|
||||
|
||||
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract runOnWorkspace(args: RunOnWorkspaceArgs): Promise<void>;
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { CommandRunner, Option } from 'nest-commander';
|
||||
|
||||
import { CommandLogger } from 'src/database/commands/logger';
|
||||
|
||||
export type MigrationCommandOptions = {
|
||||
dryRun?: boolean;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export abstract class MigrationCommandRunner extends CommandRunner {
|
||||
protected logger: CommandLogger | Logger;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.logger = new CommandLogger({
|
||||
verbose: false,
|
||||
constructorName: this.constructor.name,
|
||||
});
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-d, --dry-run',
|
||||
description: 'Simulate the command without making actual changes',
|
||||
required: false,
|
||||
})
|
||||
parseDryRun(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-v, --verbose',
|
||||
description: 'Verbose output',
|
||||
required: false,
|
||||
})
|
||||
parseVerbose(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
override async run(
|
||||
passedParams: string[],
|
||||
options: MigrationCommandOptions,
|
||||
): Promise<void> {
|
||||
if (options.verbose) {
|
||||
this.logger = new CommandLogger({
|
||||
verbose: true,
|
||||
constructorName: this.constructor.name,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await this.runMigrationCommand(passedParams, options);
|
||||
} catch (error) {
|
||||
this.logger.error(chalk.red(`Command failed`));
|
||||
throw error;
|
||||
} finally {
|
||||
this.logger.log(chalk.blue('Command completed!'));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract runMigrationCommand(
|
||||
passedParams: string[],
|
||||
options: MigrationCommandOptions,
|
||||
): Promise<void>;
|
||||
}
|
||||
Reference in New Issue
Block a user