Files
twenty/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts
T
Paul Rastoin d2dda67596 Fix upgrade --start-from-workspace-id (#20116)
# Introduction
Prevent using both `--start-from-workspace-id` and `--workspace`

When any of the two are being passed we prevent passing to the next
instance segment, it would require an upgrade re run even if legit

When `--start-from-workspace-id` is passed we filter from all the
fetched active or suspended workspace ids and apply equivalent filter as
before
2026-04-28 16:46:15 +00:00

169 lines
4.4 KiB
TypeScript

import chalk from 'chalk';
import { Command, CommandRunner, Option } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { CommandLogger } from 'src/database/commands/logger';
import { UpgradeSequenceReaderService } from 'src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service';
import { UpgradeSequenceRunnerService } from 'src/engine/core-modules/upgrade/services/upgrade-sequence-runner.service';
type RawUpgradeCommandOptions = {
workspaceId?: Set<string>;
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
dryRun?: boolean;
verbose?: boolean;
};
export type ParsedUpgradeCommandOptions = {
workspaceIds?: string[];
startFromWorkspaceId?: string;
workspaceCountLimit?: number;
dryRun?: boolean;
verbose?: boolean;
};
@Command({
name: 'upgrade',
description: 'Upgrade workspaces to the latest version',
})
export class UpgradeCommand extends CommandRunner {
protected logger: CommandLogger;
constructor(
protected readonly upgradeSequenceReaderService: UpgradeSequenceReaderService,
protected readonly upgradeSequenceRunnerService: UpgradeSequenceRunnerService,
) {
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;
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description:
'workspace id. Command runs on all active/suspended workspaces if not provided.',
required: false,
})
parseWorkspaceId(val: string, previous?: Set<string>): Set<string> {
const accumulator = previous ?? new Set<string>();
accumulator.add(val);
return accumulator;
}
@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 {
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 {
const limit = parseInt(val);
if (isNaN(limit)) {
throw new Error('Workspace count limit must be a number');
}
if (limit <= 0) {
throw new Error('Workspace count limit must be greater than 0');
}
return limit;
}
override async run(
_passedParams: string[],
options: RawUpgradeCommandOptions,
): Promise<void> {
if (options.verbose) {
this.logger = new CommandLogger({
verbose: true,
constructorName: this.constructor.name,
});
}
if (
isDefined(options.workspaceId) &&
isDefined(options.startFromWorkspaceId)
) {
throw new Error(
'Cannot use --start-from-workspace-id together with -w/--workspace-id',
);
}
try {
const sequence = this.upgradeSequenceReaderService.getUpgradeSequence();
this.logger.log(
chalk.blue(
[
'Initialized upgrade sequence:',
`- ${sequence.length} step(s)`,
...sequence.map(
(step, index) =>
` [${index}] ${step.kind}${step.name} (${step.version})`,
),
].join('\n '),
),
);
const { totalSuccesses, totalFailures } =
await this.upgradeSequenceRunnerService.run({
sequence,
options: {
...options,
workspaceIds: isDefined(options.workspaceId)
? Array.from(options.workspaceId)
: undefined,
},
});
this.logger.log(
chalk.blue(
`Upgrade summary: ${totalSuccesses} workspace(s) succeeded, ${totalFailures} workspace(s) failed`,
),
);
if (totalFailures > 0) {
throw new Error(
`Upgrade completed with ${totalFailures} workspace failure(s)`,
);
}
} catch (error) {
this.logger.error(chalk.red(`Upgrade failed: ${error.message}`));
throw error;
}
}
}